diff options
Diffstat (limited to 'server')
488 files changed, 13134 insertions, 5737 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 62412cd62..e44f1c6ab 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | // Intercept ActivityPub client requests | ||
2 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as cors from 'cors' | ||
3 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' | 3 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' |
4 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' | 4 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' |
5 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants' | 5 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants' |
@@ -14,7 +14,7 @@ import { | |||
14 | videosCustomGetValidator, | 14 | videosCustomGetValidator, |
15 | videosShareValidator | 15 | videosShareValidator |
16 | } from '../../middlewares' | 16 | } from '../../middlewares' |
17 | import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' | 17 | import { getAccountVideoRateValidatorFactory, videoCommentGetValidator } from '../../middlewares/validators' |
18 | import { AccountModel } from '../../models/account/account' | 18 | import { AccountModel } from '../../models/account/account' |
19 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 19 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
20 | import { VideoModel } from '../../models/video/video' | 20 | import { VideoModel } from '../../models/video/video' |
@@ -24,22 +24,25 @@ import { cacheRoute } from '../../middlewares/cache' | |||
24 | import { activityPubResponse } from './utils' | 24 | import { activityPubResponse } from './utils' |
25 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | 25 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' |
26 | import { | 26 | import { |
27 | getRateUrl, | ||
28 | getVideoCommentsActivityPubUrl, | 27 | getVideoCommentsActivityPubUrl, |
29 | getVideoDislikesActivityPubUrl, | 28 | getVideoDislikesActivityPubUrl, |
30 | getVideoLikesActivityPubUrl, | 29 | getVideoLikesActivityPubUrl, |
31 | getVideoSharesActivityPubUrl | 30 | getVideoSharesActivityPubUrl |
32 | } from '../../lib/activitypub' | 31 | } from '../../lib/activitypub/url' |
33 | import { VideoCaptionModel } from '../../models/video/video-caption' | 32 | import { VideoCaptionModel } from '../../models/video/video-caption' |
34 | import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy' | 33 | import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy' |
35 | import { getServerActor } from '../../helpers/utils' | ||
36 | import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' | 34 | import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' |
37 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' | 35 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' |
38 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 36 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
39 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 37 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
40 | import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models' | 38 | import { MAccountId, MActorId, MVideoAPWithoutCaption, MVideoId } from '@server/typings/models' |
39 | import { getServerActor } from '@server/models/application/application' | ||
40 | import { getRateUrl } from '@server/lib/activitypub/video-rates' | ||
41 | 41 | ||
42 | const activityPubClientRouter = express.Router() | 42 | const activityPubClientRouter = express.Router() |
43 | activityPubClientRouter.use(cors()) | ||
44 | |||
45 | // Intercept ActivityPub client requests | ||
43 | 46 | ||
44 | activityPubClientRouter.get('/accounts?/:name', | 47 | activityPubClientRouter.get('/accounts?/:name', |
45 | executeIfActivityPub, | 48 | executeIfActivityPub, |
@@ -63,13 +66,13 @@ activityPubClientRouter.get('/accounts?/:name/playlists', | |||
63 | ) | 66 | ) |
64 | activityPubClientRouter.get('/accounts?/:name/likes/:videoId', | 67 | activityPubClientRouter.get('/accounts?/:name/likes/:videoId', |
65 | executeIfActivityPub, | 68 | executeIfActivityPub, |
66 | asyncMiddleware(getAccountVideoRateValidator('like')), | 69 | asyncMiddleware(getAccountVideoRateValidatorFactory('like')), |
67 | getAccountVideoRate('like') | 70 | getAccountVideoRateFactory('like') |
68 | ) | 71 | ) |
69 | activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', | 72 | activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', |
70 | executeIfActivityPub, | 73 | executeIfActivityPub, |
71 | asyncMiddleware(getAccountVideoRateValidator('dislike')), | 74 | asyncMiddleware(getAccountVideoRateValidatorFactory('dislike')), |
72 | getAccountVideoRate('dislike') | 75 | getAccountVideoRateFactory('dislike') |
73 | ) | 76 | ) |
74 | 77 | ||
75 | activityPubClientRouter.get('/videos/watch/:id', | 78 | activityPubClientRouter.get('/videos/watch/:id', |
@@ -85,7 +88,7 @@ activityPubClientRouter.get('/videos/watch/:id/activity', | |||
85 | ) | 88 | ) |
86 | activityPubClientRouter.get('/videos/watch/:id/announces', | 89 | activityPubClientRouter.get('/videos/watch/:id/announces', |
87 | executeIfActivityPub, | 90 | executeIfActivityPub, |
88 | asyncMiddleware(videosCustomGetValidator('only-video')), | 91 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
89 | asyncMiddleware(videoAnnouncesController) | 92 | asyncMiddleware(videoAnnouncesController) |
90 | ) | 93 | ) |
91 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', | 94 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', |
@@ -95,17 +98,17 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', | |||
95 | ) | 98 | ) |
96 | activityPubClientRouter.get('/videos/watch/:id/likes', | 99 | activityPubClientRouter.get('/videos/watch/:id/likes', |
97 | executeIfActivityPub, | 100 | executeIfActivityPub, |
98 | asyncMiddleware(videosCustomGetValidator('only-video')), | 101 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
99 | asyncMiddleware(videoLikesController) | 102 | asyncMiddleware(videoLikesController) |
100 | ) | 103 | ) |
101 | activityPubClientRouter.get('/videos/watch/:id/dislikes', | 104 | activityPubClientRouter.get('/videos/watch/:id/dislikes', |
102 | executeIfActivityPub, | 105 | executeIfActivityPub, |
103 | asyncMiddleware(videosCustomGetValidator('only-video')), | 106 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
104 | asyncMiddleware(videoDislikesController) | 107 | asyncMiddleware(videoDislikesController) |
105 | ) | 108 | ) |
106 | activityPubClientRouter.get('/videos/watch/:id/comments', | 109 | activityPubClientRouter.get('/videos/watch/:id/comments', |
107 | executeIfActivityPub, | 110 | executeIfActivityPub, |
108 | asyncMiddleware(videosCustomGetValidator('only-video')), | 111 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
109 | asyncMiddleware(videoCommentsController) | 112 | asyncMiddleware(videoCommentsController) |
110 | ) | 113 | ) |
111 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', | 114 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', |
@@ -122,7 +125,7 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity | |||
122 | activityPubClientRouter.get('/video-channels/:name', | 125 | activityPubClientRouter.get('/video-channels/:name', |
123 | executeIfActivityPub, | 126 | executeIfActivityPub, |
124 | asyncMiddleware(localVideoChannelValidator), | 127 | asyncMiddleware(localVideoChannelValidator), |
125 | asyncMiddleware(videoChannelController) | 128 | videoChannelController |
126 | ) | 129 | ) |
127 | activityPubClientRouter.get('/video-channels/:name/followers', | 130 | activityPubClientRouter.get('/video-channels/:name/followers', |
128 | executeIfActivityPub, | 131 | executeIfActivityPub, |
@@ -154,7 +157,7 @@ activityPubClientRouter.get('/video-playlists/:playlistId', | |||
154 | activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', | 157 | activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', |
155 | executeIfActivityPub, | 158 | executeIfActivityPub, |
156 | asyncMiddleware(videoPlaylistElementAPGetValidator), | 159 | asyncMiddleware(videoPlaylistElementAPGetValidator), |
157 | asyncMiddleware(videoPlaylistElementController) | 160 | videoPlaylistElementController |
158 | ) | 161 | ) |
159 | 162 | ||
160 | // --------------------------------------------------------------------------- | 163 | // --------------------------------------------------------------------------- |
@@ -192,7 +195,7 @@ async function accountPlaylistsController (req: express.Request, res: express.Re | |||
192 | return activityPubResponse(activityPubContextify(activityPubResult), res) | 195 | return activityPubResponse(activityPubContextify(activityPubResult), res) |
193 | } | 196 | } |
194 | 197 | ||
195 | function getAccountVideoRate (rateType: VideoRateType) { | 198 | function getAccountVideoRateFactory (rateType: VideoRateType) { |
196 | return (req: express.Request, res: express.Response) => { | 199 | return (req: express.Request, res: express.Response) => { |
197 | const accountVideoRate = res.locals.accountVideoRate | 200 | const accountVideoRate = res.locals.accountVideoRate |
198 | 201 | ||
@@ -234,11 +237,11 @@ async function videoAnnounceController (req: express.Request, res: express.Respo | |||
234 | 237 | ||
235 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined) | 238 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined) |
236 | 239 | ||
237 | return activityPubResponse(activityPubContextify(activity), res) | 240 | return activityPubResponse(activityPubContextify(activity, 'Announce'), res) |
238 | } | 241 | } |
239 | 242 | ||
240 | async function videoAnnouncesController (req: express.Request, res: express.Response) { | 243 | async function videoAnnouncesController (req: express.Request, res: express.Response) { |
241 | const video = res.locals.onlyVideo | 244 | const video = res.locals.onlyImmutableVideo |
242 | 245 | ||
243 | const handler = async (start: number, count: number) => { | 246 | const handler = async (start: number, count: number) => { |
244 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) | 247 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) |
@@ -253,21 +256,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp | |||
253 | } | 256 | } |
254 | 257 | ||
255 | async function videoLikesController (req: express.Request, res: express.Response) { | 258 | async function videoLikesController (req: express.Request, res: express.Response) { |
256 | const video = res.locals.onlyVideo | 259 | const video = res.locals.onlyImmutableVideo |
257 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) | 260 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) |
258 | 261 | ||
259 | return activityPubResponse(activityPubContextify(json), res) | 262 | return activityPubResponse(activityPubContextify(json), res) |
260 | } | 263 | } |
261 | 264 | ||
262 | async function videoDislikesController (req: express.Request, res: express.Response) { | 265 | async function videoDislikesController (req: express.Request, res: express.Response) { |
263 | const video = res.locals.onlyVideo | 266 | const video = res.locals.onlyImmutableVideo |
264 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) | 267 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) |
265 | 268 | ||
266 | return activityPubResponse(activityPubContextify(json), res) | 269 | return activityPubResponse(activityPubContextify(json), res) |
267 | } | 270 | } |
268 | 271 | ||
269 | async function videoCommentsController (req: express.Request, res: express.Response) { | 272 | async function videoCommentsController (req: express.Request, res: express.Response) { |
270 | const video = res.locals.onlyVideo | 273 | const video = res.locals.onlyImmutableVideo |
271 | 274 | ||
272 | const handler = async (start: number, count: number) => { | 275 | const handler = async (start: number, count: number) => { |
273 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) | 276 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) |
@@ -281,7 +284,7 @@ async function videoCommentsController (req: express.Request, res: express.Respo | |||
281 | return activityPubResponse(activityPubContextify(json), res) | 284 | return activityPubResponse(activityPubContextify(json), res) |
282 | } | 285 | } |
283 | 286 | ||
284 | async function videoChannelController (req: express.Request, res: express.Response) { | 287 | function videoChannelController (req: express.Request, res: express.Response) { |
285 | const videoChannel = res.locals.videoChannel | 288 | const videoChannel = res.locals.videoChannel |
286 | 289 | ||
287 | return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res) | 290 | return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res) |
@@ -334,10 +337,10 @@ async function videoRedundancyController (req: express.Request, res: express.Res | |||
334 | 337 | ||
335 | if (req.path.endsWith('/activity')) { | 338 | if (req.path.endsWith('/activity')) { |
336 | const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience) | 339 | const data = buildCreateActivity(videoRedundancy.url, serverActor, object, audience) |
337 | return activityPubResponse(activityPubContextify(data), res) | 340 | return activityPubResponse(activityPubContextify(data, 'CacheFile'), res) |
338 | } | 341 | } |
339 | 342 | ||
340 | return activityPubResponse(activityPubContextify(object), res) | 343 | return activityPubResponse(activityPubContextify(object, 'CacheFile'), res) |
341 | } | 344 | } |
342 | 345 | ||
343 | async function videoPlaylistController (req: express.Request, res: express.Response) { | 346 | async function videoPlaylistController (req: express.Request, res: express.Response) { |
@@ -353,7 +356,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo | |||
353 | return activityPubResponse(activityPubContextify(object), res) | 356 | return activityPubResponse(activityPubContextify(object), res) |
354 | } | 357 | } |
355 | 358 | ||
356 | async function videoPlaylistElementController (req: express.Request, res: express.Response) { | 359 | function videoPlaylistElementController (req: express.Request, res: express.Response) { |
357 | const videoPlaylistElement = res.locals.videoPlaylistElementAP | 360 | const videoPlaylistElement = res.locals.videoPlaylistElementAP |
358 | 361 | ||
359 | const json = videoPlaylistElement.toActivityPubObject() | 362 | const json = videoPlaylistElement.toActivityPubObject() |
@@ -386,7 +389,7 @@ async function actorPlaylists (req: express.Request, account: MAccountId) { | |||
386 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | 389 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) |
387 | } | 390 | } |
388 | 391 | ||
389 | function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) { | 392 | function videoRates (req: express.Request, rateType: VideoRateType, video: MVideoId, url: string) { |
390 | const handler = async (start: number, count: number) => { | 393 | const handler = async (start: number, count: number) => { |
391 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) | 394 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) |
392 | return { | 395 | return { |
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts index ca42106b8..c5edf86b7 100644 --- a/server/controllers/activitypub/inbox.ts +++ b/server/controllers/activitypub/inbox.ts | |||
@@ -46,15 +46,19 @@ const inboxQueue = queue<QueueParam, Error>((task, cb) => { | |||
46 | 46 | ||
47 | processActivities(task.activities, options) | 47 | processActivities(task.activities, options) |
48 | .then(() => cb()) | 48 | .then(() => cb()) |
49 | .catch(err => { | ||
50 | logger.error('Error in process activities.', { err }) | ||
51 | cb() | ||
52 | }) | ||
49 | }) | 53 | }) |
50 | 54 | ||
51 | function inboxController (req: express.Request, res: express.Response) { | 55 | function inboxController (req: express.Request, res: express.Response) { |
52 | const rootActivity: RootActivity = req.body | 56 | const rootActivity: RootActivity = req.body |
53 | let activities: Activity[] = [] | 57 | let activities: Activity[] |
54 | 58 | ||
55 | if ([ 'Collection', 'CollectionPage' ].indexOf(rootActivity.type) !== -1) { | 59 | if ([ 'Collection', 'CollectionPage' ].includes(rootActivity.type)) { |
56 | activities = (rootActivity as ActivityPubCollection).items | 60 | activities = (rootActivity as ActivityPubCollection).items |
57 | } else if ([ 'OrderedCollection', 'OrderedCollectionPage' ].indexOf(rootActivity.type) !== -1) { | 61 | } else if ([ 'OrderedCollection', 'OrderedCollectionPage' ].includes(rootActivity.type)) { |
58 | activities = (rootActivity as ActivityPubOrderedCollection<Activity>).orderedItems | 62 | activities = (rootActivity as ActivityPubOrderedCollection<Activity>).orderedItems |
59 | } else { | 63 | } else { |
60 | activities = [ rootActivity as Activity ] | 64 | activities = [ rootActivity as Activity ] |
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts index 05740318e..ccdc610a2 100644 --- a/server/controllers/api/accounts.ts +++ b/server/controllers/api/accounts.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' | 2 | import { getFormattedObjects } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | authenticate, | 5 | authenticate, |
@@ -16,21 +16,19 @@ import { | |||
16 | accountNameWithHostGetValidator, | 16 | accountNameWithHostGetValidator, |
17 | accountsSortValidator, | 17 | accountsSortValidator, |
18 | ensureAuthUserOwnsAccountValidator, | 18 | ensureAuthUserOwnsAccountValidator, |
19 | videoChannelsSortValidator, | ||
19 | videosSortValidator, | 20 | videosSortValidator, |
20 | videoChannelsSortValidator | 21 | videoChannelStatsValidator |
21 | } from '../../middlewares/validators' | 22 | } from '../../middlewares/validators' |
22 | import { AccountModel } from '../../models/account/account' | 23 | import { AccountModel } from '../../models/account/account' |
23 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | 24 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' |
24 | import { VideoModel } from '../../models/video/video' | 25 | import { VideoModel } from '../../models/video/video' |
25 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI, getCountVideos } from '../../helpers/express-utils' | 26 | import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
26 | import { VideoChannelModel } from '../../models/video/video-channel' | 27 | import { VideoChannelModel } from '../../models/video/video-channel' |
27 | import { JobQueue } from '../../lib/job-queue' | 28 | import { JobQueue } from '../../lib/job-queue' |
28 | import { logger } from '../../helpers/logger' | ||
29 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 29 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
30 | import { | 30 | import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists' |
31 | commonVideoPlaylistFiltersValidator, | 31 | import { getServerActor } from '@server/models/application/application' |
32 | videoPlaylistsSearchValidator | ||
33 | } from '../../middlewares/validators/videos/video-playlists' | ||
34 | 32 | ||
35 | const accountsRouter = express.Router() | 33 | const accountsRouter = express.Router() |
36 | 34 | ||
@@ -60,6 +58,7 @@ accountsRouter.get('/:accountName/videos', | |||
60 | 58 | ||
61 | accountsRouter.get('/:accountName/video-channels', | 59 | accountsRouter.get('/:accountName/video-channels', |
62 | asyncMiddleware(accountNameWithHostGetValidator), | 60 | asyncMiddleware(accountNameWithHostGetValidator), |
61 | videoChannelStatsValidator, | ||
63 | paginationValidator, | 62 | paginationValidator, |
64 | videoChannelsSortValidator, | 63 | videoChannelsSortValidator, |
65 | setDefaultSort, | 64 | setDefaultSort, |
@@ -104,7 +103,6 @@ function getAccount (req: express.Request, res: express.Response) { | |||
104 | 103 | ||
105 | if (account.isOutdated()) { | 104 | if (account.isOutdated()) { |
106 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } }) | 105 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } }) |
107 | .catch(err => logger.error('Cannot create AP refresher job for actor %s.', account.Actor.url, { err })) | ||
108 | } | 106 | } |
109 | 107 | ||
110 | return res.json(account.toFormattedJSON()) | 108 | return res.json(account.toFormattedJSON()) |
@@ -121,7 +119,8 @@ async function listAccountChannels (req: express.Request, res: express.Response) | |||
121 | accountId: res.locals.account.id, | 119 | accountId: res.locals.account.id, |
122 | start: req.query.start, | 120 | start: req.query.start, |
123 | count: req.query.count, | 121 | count: req.query.count, |
124 | sort: req.query.sort | 122 | sort: req.query.sort, |
123 | withStats: req.query.withStats | ||
125 | } | 124 | } |
126 | 125 | ||
127 | const resultList = await VideoChannelModel.listByAccount(options) | 126 | const resultList = await VideoChannelModel.listByAccount(options) |
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index ae4e00248..edcb0b99e 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -1,23 +1,22 @@ | |||
1 | import { Hooks } from '@server/lib/plugins/hooks' | ||
1 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { remove, writeJSON } from 'fs-extra' | ||
2 | import { snakeCase } from 'lodash' | 4 | import { snakeCase } from 'lodash' |
3 | import { ServerConfig, UserRight } from '../../../shared' | 5 | import validator from 'validator' |
6 | import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig, UserRight } from '../../../shared' | ||
4 | import { About } from '../../../shared/models/server/about.model' | 7 | import { About } from '../../../shared/models/server/about.model' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 8 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
9 | import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger' | ||
10 | import { objectConverter } from '../../helpers/core-utils' | ||
6 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | 11 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' |
12 | import { getServerCommit } from '../../helpers/utils' | ||
13 | import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config' | ||
7 | import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants' | 14 | import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants' |
8 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' | ||
9 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' | ||
10 | import { ClientHtml } from '../../lib/client-html' | 15 | import { ClientHtml } from '../../lib/client-html' |
11 | import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger' | ||
12 | import { remove, writeJSON } from 'fs-extra' | ||
13 | import { getServerCommit } from '../../helpers/utils' | ||
14 | import { Emailer } from '../../lib/emailer' | ||
15 | import validator from 'validator' | ||
16 | import { objectConverter } from '../../helpers/core-utils' | ||
17 | import { CONFIG, reloadConfig } from '../../initializers/config' | ||
18 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 16 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
19 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' | 17 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' |
20 | import { Hooks } from '@server/lib/plugins/hooks' | 18 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' |
19 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' | ||
21 | 20 | ||
22 | const configRouter = express.Router() | 21 | const configRouter = express.Router() |
23 | 22 | ||
@@ -31,12 +30,12 @@ configRouter.get('/', | |||
31 | configRouter.get('/custom', | 30 | configRouter.get('/custom', |
32 | authenticate, | 31 | authenticate, |
33 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), | 32 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), |
34 | asyncMiddleware(getCustomConfig) | 33 | getCustomConfig |
35 | ) | 34 | ) |
36 | configRouter.put('/custom', | 35 | configRouter.put('/custom', |
37 | authenticate, | 36 | authenticate, |
38 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), | 37 | ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), |
39 | asyncMiddleware(customConfigUpdateValidator), | 38 | customConfigUpdateValidator, |
40 | asyncMiddleware(updateCustomConfig) | 39 | asyncMiddleware(updateCustomConfig) |
41 | ) | 40 | ) |
42 | configRouter.delete('/custom', | 41 | configRouter.delete('/custom', |
@@ -73,15 +72,23 @@ async function getConfig (req: express.Request, res: express.Response) { | |||
73 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS | 72 | css: CONFIG.INSTANCE.CUSTOMIZATIONS.CSS |
74 | } | 73 | } |
75 | }, | 74 | }, |
75 | search: { | ||
76 | remoteUri: { | ||
77 | users: CONFIG.SEARCH.REMOTE_URI.USERS, | ||
78 | anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS | ||
79 | } | ||
80 | }, | ||
76 | plugin: { | 81 | plugin: { |
77 | registered: getRegisteredPlugins() | 82 | registered: getRegisteredPlugins(), |
83 | registeredExternalAuths: getExternalAuthsPlugins(), | ||
84 | registeredIdAndPassAuths: getIdAndPassAuthPlugins() | ||
78 | }, | 85 | }, |
79 | theme: { | 86 | theme: { |
80 | registered: getRegisteredThemes(), | 87 | registered: getRegisteredThemes(), |
81 | default: defaultTheme | 88 | default: defaultTheme |
82 | }, | 89 | }, |
83 | email: { | 90 | email: { |
84 | enabled: Emailer.isEnabled() | 91 | enabled: isEmailEnabled() |
85 | }, | 92 | }, |
86 | contactForm: { | 93 | contactForm: { |
87 | enabled: CONFIG.CONTACT_FORM.ENABLED | 94 | enabled: CONFIG.CONTACT_FORM.ENABLED |
@@ -196,7 +203,7 @@ function getAbout (req: express.Request, res: express.Response) { | |||
196 | return res.json(about).end() | 203 | return res.json(about).end() |
197 | } | 204 | } |
198 | 205 | ||
199 | async function getCustomConfig (req: express.Request, res: express.Response) { | 206 | function getCustomConfig (req: express.Request, res: express.Response) { |
200 | const data = customConfig() | 207 | const data = customConfig() |
201 | 208 | ||
202 | return res.json(data).end() | 209 | return res.json(data).end() |
@@ -250,7 +257,7 @@ function getRegisteredThemes () { | |||
250 | 257 | ||
251 | function getEnabledResolutions () { | 258 | function getEnabledResolutions () { |
252 | return Object.keys(CONFIG.TRANSCODING.RESOLUTIONS) | 259 | return Object.keys(CONFIG.TRANSCODING.RESOLUTIONS) |
253 | .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[ key ] === true) | 260 | .filter(key => CONFIG.TRANSCODING.ENABLED && CONFIG.TRANSCODING.RESOLUTIONS[key] === true) |
254 | .map(r => parseInt(r, 10)) | 261 | .map(r => parseInt(r, 10)) |
255 | } | 262 | } |
256 | 263 | ||
@@ -264,6 +271,42 @@ function getRegisteredPlugins () { | |||
264 | })) | 271 | })) |
265 | } | 272 | } |
266 | 273 | ||
274 | function getIdAndPassAuthPlugins () { | ||
275 | const result: RegisteredIdAndPassAuthConfig[] = [] | ||
276 | |||
277 | for (const p of PluginManager.Instance.getIdAndPassAuths()) { | ||
278 | for (const auth of p.idAndPassAuths) { | ||
279 | result.push({ | ||
280 | npmName: p.npmName, | ||
281 | name: p.name, | ||
282 | version: p.version, | ||
283 | authName: auth.authName, | ||
284 | weight: auth.getWeight() | ||
285 | }) | ||
286 | } | ||
287 | } | ||
288 | |||
289 | return result | ||
290 | } | ||
291 | |||
292 | function getExternalAuthsPlugins () { | ||
293 | const result: RegisteredExternalAuthConfig[] = [] | ||
294 | |||
295 | for (const p of PluginManager.Instance.getExternalAuths()) { | ||
296 | for (const auth of p.externalAuths) { | ||
297 | result.push({ | ||
298 | npmName: p.npmName, | ||
299 | name: p.name, | ||
300 | version: p.version, | ||
301 | authName: auth.authName, | ||
302 | authDisplayName: auth.authDisplayName() | ||
303 | }) | ||
304 | } | ||
305 | } | ||
306 | |||
307 | return result | ||
308 | } | ||
309 | |||
267 | // --------------------------------------------------------------------------- | 310 | // --------------------------------------------------------------------------- |
268 | 311 | ||
269 | export { | 312 | export { |
@@ -340,13 +383,13 @@ function customConfig (): CustomConfig { | |||
340 | allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES, | 383 | allowAudioFiles: CONFIG.TRANSCODING.ALLOW_AUDIO_FILES, |
341 | threads: CONFIG.TRANSCODING.THREADS, | 384 | threads: CONFIG.TRANSCODING.THREADS, |
342 | resolutions: { | 385 | resolutions: { |
343 | '0p': CONFIG.TRANSCODING.RESOLUTIONS[ '0p' ], | 386 | '0p': CONFIG.TRANSCODING.RESOLUTIONS['0p'], |
344 | '240p': CONFIG.TRANSCODING.RESOLUTIONS[ '240p' ], | 387 | '240p': CONFIG.TRANSCODING.RESOLUTIONS['240p'], |
345 | '360p': CONFIG.TRANSCODING.RESOLUTIONS[ '360p' ], | 388 | '360p': CONFIG.TRANSCODING.RESOLUTIONS['360p'], |
346 | '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ], | 389 | '480p': CONFIG.TRANSCODING.RESOLUTIONS['480p'], |
347 | '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], | 390 | '720p': CONFIG.TRANSCODING.RESOLUTIONS['720p'], |
348 | '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ], | 391 | '1080p': CONFIG.TRANSCODING.RESOLUTIONS['1080p'], |
349 | '2160p': CONFIG.TRANSCODING.RESOLUTIONS[ '2160p' ] | 392 | '2160p': CONFIG.TRANSCODING.RESOLUTIONS['2160p'] |
350 | }, | 393 | }, |
351 | webtorrent: { | 394 | webtorrent: { |
352 | enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED | 395 | enabled: CONFIG.TRANSCODING.WEBTORRENT.ENABLED |
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts index 6138a32de..7bec6c527 100644 --- a/server/controllers/api/index.ts +++ b/server/controllers/api/index.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as RateLimit from 'express-rate-limit' | ||
3 | import { configRouter } from './config' | 2 | import { configRouter } from './config' |
4 | import { jobsRouter } from './jobs' | 3 | import { jobsRouter } from './jobs' |
5 | import { oauthClientsRouter } from './oauth-clients' | 4 | import { oauthClientsRouter } from './oauth-clients' |
@@ -15,6 +14,7 @@ import { overviewsRouter } from './overviews' | |||
15 | import { videoPlaylistRouter } from './video-playlist' | 14 | import { videoPlaylistRouter } from './video-playlist' |
16 | import { CONFIG } from '../../initializers/config' | 15 | import { CONFIG } from '../../initializers/config' |
17 | import { pluginRouter } from './plugins' | 16 | import { pluginRouter } from './plugins' |
17 | import * as RateLimit from 'express-rate-limit' | ||
18 | 18 | ||
19 | const apiRouter = express.Router() | 19 | const apiRouter = express.Router() |
20 | 20 | ||
@@ -24,8 +24,6 @@ apiRouter.use(cors({ | |||
24 | credentials: true | 24 | credentials: true |
25 | })) | 25 | })) |
26 | 26 | ||
27 | // FIXME: https://github.com/nfriedly/express-rate-limit/issues/138 | ||
28 | // @ts-ignore | ||
29 | const apiRateLimiter = RateLimit({ | 27 | const apiRateLimiter = RateLimit({ |
30 | windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, | 28 | windowMs: CONFIG.RATES_LIMIT.API.WINDOW_MS, |
31 | max: CONFIG.RATES_LIMIT.API.MAX | 29 | max: CONFIG.RATES_LIMIT.API.MAX |
diff --git a/server/controllers/api/jobs.ts b/server/controllers/api/jobs.ts index 05320311e..13fc04d18 100644 --- a/server/controllers/api/jobs.ts +++ b/server/controllers/api/jobs.ts | |||
@@ -50,7 +50,7 @@ async function listJobs (req: express.Request, res: express.Response) { | |||
50 | }) | 50 | }) |
51 | const total = await JobQueue.Instance.count(state) | 51 | const total = await JobQueue.Instance.count(state) |
52 | 52 | ||
53 | const result: ResultList<any> = { | 53 | const result: ResultList<Job> = { |
54 | total, | 54 | total, |
55 | data: jobs.map(j => formatJob(j, state)) | 55 | data: jobs.map(j => formatJob(j, state)) |
56 | } | 56 | } |
diff --git a/server/controllers/api/overviews.ts b/server/controllers/api/overviews.ts index 46e76ac6b..fb31932aa 100644 --- a/server/controllers/api/overviews.ts +++ b/server/controllers/api/overviews.ts | |||
@@ -1,17 +1,18 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { buildNSFWFilter } from '../../helpers/express-utils' | 2 | import { buildNSFWFilter } from '../../helpers/express-utils' |
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { asyncMiddleware } from '../../middlewares' | 4 | import { asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares' |
5 | import { TagModel } from '../../models/video/tag' | 5 | import { TagModel } from '../../models/video/tag' |
6 | import { VideosOverview } from '../../../shared/models/overviews' | 6 | import { CategoryOverview, ChannelOverview, TagOverview, VideosOverview } from '../../../shared/models/overviews' |
7 | import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers/constants' | 7 | import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants' |
8 | import { cacheRoute } from '../../middlewares/cache' | ||
9 | import * as memoizee from 'memoizee' | 8 | import * as memoizee from 'memoizee' |
9 | import { logger } from '@server/helpers/logger' | ||
10 | 10 | ||
11 | const overviewsRouter = express.Router() | 11 | const overviewsRouter = express.Router() |
12 | 12 | ||
13 | overviewsRouter.get('/videos', | 13 | overviewsRouter.get('/videos', |
14 | asyncMiddleware(cacheRoute()(ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS)), | 14 | videosOverviewValidator, |
15 | optionalAuthenticate, | ||
15 | asyncMiddleware(getVideosOverview) | 16 | asyncMiddleware(getVideosOverview) |
16 | ) | 17 | ) |
17 | 18 | ||
@@ -24,21 +25,32 @@ export { overviewsRouter } | |||
24 | const buildSamples = memoizee(async function () { | 25 | const buildSamples = memoizee(async function () { |
25 | const [ categories, channels, tags ] = await Promise.all([ | 26 | const [ categories, channels, tags ] = await Promise.all([ |
26 | VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT), | 27 | VideoModel.getRandomFieldSamples('category', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT), |
27 | VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD ,OVERVIEWS.VIDEOS.SAMPLES_COUNT), | 28 | VideoModel.getRandomFieldSamples('channelId', OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT), |
28 | TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT) | 29 | TagModel.getRandomSamples(OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD, OVERVIEWS.VIDEOS.SAMPLES_COUNT) |
29 | ]) | 30 | ]) |
30 | 31 | ||
31 | return { categories, channels, tags } | 32 | const result = { categories, channels, tags } |
33 | |||
34 | logger.debug('Building samples for overview endpoint.', { result }) | ||
35 | |||
36 | return result | ||
32 | }, { maxAge: MEMOIZE_TTL.OVERVIEWS_SAMPLE }) | 37 | }, { maxAge: MEMOIZE_TTL.OVERVIEWS_SAMPLE }) |
33 | 38 | ||
34 | // This endpoint could be quite long, but we cache it | 39 | // This endpoint could be quite long, but we cache it |
35 | async function getVideosOverview (req: express.Request, res: express.Response) { | 40 | async function getVideosOverview (req: express.Request, res: express.Response) { |
36 | const attributes = await buildSamples() | 41 | const attributes = await buildSamples() |
37 | 42 | ||
38 | const [ categories, channels, tags ] = await Promise.all([ | 43 | const page = req.query.page || 1 |
39 | Promise.all(attributes.categories.map(c => getVideosByCategory(c, res))), | 44 | const index = page - 1 |
40 | Promise.all(attributes.channels.map(c => getVideosByChannel(c, res))), | 45 | |
41 | Promise.all(attributes.tags.map(t => getVideosByTag(t, res))) | 46 | const categories: CategoryOverview[] = [] |
47 | const channels: ChannelOverview[] = [] | ||
48 | const tags: TagOverview[] = [] | ||
49 | |||
50 | await Promise.all([ | ||
51 | getVideosByCategory(attributes.categories, index, res, categories), | ||
52 | getVideosByChannel(attributes.channels, index, res, channels), | ||
53 | getVideosByTag(attributes.tags, index, res, tags) | ||
42 | ]) | 54 | ]) |
43 | 55 | ||
44 | const result: VideosOverview = { | 56 | const result: VideosOverview = { |
@@ -47,45 +59,49 @@ async function getVideosOverview (req: express.Request, res: express.Response) { | |||
47 | tags | 59 | tags |
48 | } | 60 | } |
49 | 61 | ||
50 | // Cleanup our object | ||
51 | for (const key of Object.keys(result)) { | ||
52 | result[key] = result[key].filter(v => v !== undefined) | ||
53 | } | ||
54 | |||
55 | return res.json(result) | 62 | return res.json(result) |
56 | } | 63 | } |
57 | 64 | ||
58 | async function getVideosByTag (tag: string, res: express.Response) { | 65 | async function getVideosByTag (tagsSample: string[], index: number, res: express.Response, acc: TagOverview[]) { |
66 | if (tagsSample.length <= index) return | ||
67 | |||
68 | const tag = tagsSample[index] | ||
59 | const videos = await getVideos(res, { tagsOneOf: [ tag ] }) | 69 | const videos = await getVideos(res, { tagsOneOf: [ tag ] }) |
60 | 70 | ||
61 | if (videos.length === 0) return undefined | 71 | if (videos.length === 0) return |
62 | 72 | ||
63 | return { | 73 | acc.push({ |
64 | tag, | 74 | tag, |
65 | videos | 75 | videos |
66 | } | 76 | }) |
67 | } | 77 | } |
68 | 78 | ||
69 | async function getVideosByCategory (category: number, res: express.Response) { | 79 | async function getVideosByCategory (categoriesSample: number[], index: number, res: express.Response, acc: CategoryOverview[]) { |
80 | if (categoriesSample.length <= index) return | ||
81 | |||
82 | const category = categoriesSample[index] | ||
70 | const videos = await getVideos(res, { categoryOneOf: [ category ] }) | 83 | const videos = await getVideos(res, { categoryOneOf: [ category ] }) |
71 | 84 | ||
72 | if (videos.length === 0) return undefined | 85 | if (videos.length === 0) return |
73 | 86 | ||
74 | return { | 87 | acc.push({ |
75 | category: videos[0].category, | 88 | category: videos[0].category, |
76 | videos | 89 | videos |
77 | } | 90 | }) |
78 | } | 91 | } |
79 | 92 | ||
80 | async function getVideosByChannel (channelId: number, res: express.Response) { | 93 | async function getVideosByChannel (channelsSample: number[], index: number, res: express.Response, acc: ChannelOverview[]) { |
94 | if (channelsSample.length <= index) return | ||
95 | |||
96 | const channelId = channelsSample[index] | ||
81 | const videos = await getVideos(res, { videoChannelId: channelId }) | 97 | const videos = await getVideos(res, { videoChannelId: channelId }) |
82 | 98 | ||
83 | if (videos.length === 0) return undefined | 99 | if (videos.length === 0) return |
84 | 100 | ||
85 | return { | 101 | acc.push({ |
86 | channel: videos[0].channel, | 102 | channel: videos[0].channel, |
87 | videos | 103 | videos |
88 | } | 104 | }) |
89 | } | 105 | } |
90 | 106 | ||
91 | async function getVideos ( | 107 | async function getVideos ( |
diff --git a/server/controllers/api/plugins.ts b/server/controllers/api/plugins.ts index 6b7562fd3..f8a0d19ca 100644 --- a/server/controllers/api/plugins.ts +++ b/server/controllers/api/plugins.ts | |||
@@ -191,6 +191,8 @@ async function updatePluginSettings (req: express.Request, res: express.Response | |||
191 | plugin.settings = req.body.settings | 191 | plugin.settings = req.body.settings |
192 | await plugin.save() | 192 | await plugin.save() |
193 | 193 | ||
194 | await PluginManager.Instance.onSettingsChanged(plugin.name, plugin.settings) | ||
195 | |||
194 | return res.sendStatus(204) | 196 | return res.sendStatus(204) |
195 | } | 197 | } |
196 | 198 | ||
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts index 349650aca..35d94d747 100644 --- a/server/controllers/api/search.ts +++ b/server/controllers/api/search.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 2 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
3 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' | 3 | import { getFormattedObjects } from '../../helpers/utils' |
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
5 | import { | 5 | import { |
6 | asyncMiddleware, | 6 | asyncMiddleware, |
@@ -15,11 +15,13 @@ import { | |||
15 | videosSearchValidator | 15 | videosSearchValidator |
16 | } from '../../middlewares' | 16 | } from '../../middlewares' |
17 | import { VideoChannelsSearchQuery, VideosSearchQuery } from '../../../shared/models/search' | 17 | import { VideoChannelsSearchQuery, VideosSearchQuery } from '../../../shared/models/search' |
18 | import { getOrCreateActorAndServerAndModel, getOrCreateVideoAndAccountAndChannel } from '../../lib/activitypub' | 18 | import { getOrCreateActorAndServerAndModel } from '../../lib/activitypub/actor' |
19 | import { logger } from '../../helpers/logger' | 19 | import { logger } from '../../helpers/logger' |
20 | import { VideoChannelModel } from '../../models/video/video-channel' | 20 | import { VideoChannelModel } from '../../models/video/video-channel' |
21 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | 21 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' |
22 | import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models' | 22 | import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models' |
23 | import { getServerActor } from '@server/models/application/application' | ||
24 | import { getOrCreateVideoAndAccountAndChannel } from '@server/lib/activitypub/videos' | ||
23 | 25 | ||
24 | const searchRouter = express.Router() | 26 | const searchRouter = express.Router() |
25 | 27 | ||
@@ -60,7 +62,7 @@ function searchVideoChannels (req: express.Request, res: express.Response) { | |||
60 | 62 | ||
61 | // Handle strings like @toto@example.com | 63 | // Handle strings like @toto@example.com |
62 | if (parts.length === 3 && parts[0].length === 0) parts.shift() | 64 | if (parts.length === 3 && parts[0].length === 0) parts.shift() |
63 | const isWebfingerSearch = parts.length === 2 && parts.every(p => p && p.indexOf(' ') === -1) | 65 | const isWebfingerSearch = parts.length === 2 && parts.every(p => p && !p.includes(' ')) |
64 | 66 | ||
65 | if (isURISearch || isWebfingerSearch) return searchVideoChannelURI(search, isWebfingerSearch, res) | 67 | if (isURISearch || isWebfingerSearch) return searchVideoChannelURI(search, isWebfingerSearch, res) |
66 | 68 | ||
diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts index 4450038f6..e12fc1dd4 100644 --- a/server/controllers/api/server/debug.ts +++ b/server/controllers/api/server/debug.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight } from '../../../../shared/models/users' | 2 | import { UserRight } from '../../../../shared/models/users' |
3 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' | 3 | import { authenticate, ensureUserHasRight } from '../../../middlewares' |
4 | 4 | ||
5 | const debugRouter = express.Router() | 5 | const debugRouter = express.Router() |
6 | 6 | ||
7 | debugRouter.get('/debug', | 7 | debugRouter.get('/debug', |
8 | authenticate, | 8 | authenticate, |
9 | ensureUserHasRight(UserRight.MANAGE_DEBUG), | 9 | ensureUserHasRight(UserRight.MANAGE_DEBUG), |
10 | asyncMiddleware(getDebug) | 10 | getDebug |
11 | ) | 11 | ) |
12 | 12 | ||
13 | // --------------------------------------------------------------------------- | 13 | // --------------------------------------------------------------------------- |
@@ -18,7 +18,7 @@ export { | |||
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
21 | async function getDebug (req: express.Request, res: express.Response) { | 21 | function getDebug (req: express.Request, res: express.Response) { |
22 | return res.json({ | 22 | return res.json({ |
23 | ip: req.ip | 23 | ip: req.ip |
24 | }).end() | 24 | }).end() |
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index 29a403a43..23823c9fb 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight } from '../../../../shared/models/users' | 2 | import { UserRight } from '../../../../shared/models/users' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { SERVER_ACTOR_NAME } from '../../../initializers/constants' | 5 | import { SERVER_ACTOR_NAME } from '../../../initializers/constants' |
6 | import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' | 6 | import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' |
7 | import { | 7 | import { |
@@ -24,9 +24,10 @@ import { | |||
24 | } from '../../../middlewares/validators' | 24 | } from '../../../middlewares/validators' |
25 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 25 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
26 | import { JobQueue } from '../../../lib/job-queue' | 26 | import { JobQueue } from '../../../lib/job-queue' |
27 | import { removeRedundancyOf } from '../../../lib/redundancy' | 27 | import { removeRedundanciesOfServer } from '../../../lib/redundancy' |
28 | import { sequelizeTypescript } from '../../../initializers/database' | 28 | import { sequelizeTypescript } from '../../../initializers/database' |
29 | import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' | 29 | import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' |
30 | import { getServerActor } from '@server/models/application/application' | ||
30 | 31 | ||
31 | const serverFollowsRouter = express.Router() | 32 | const serverFollowsRouter = express.Router() |
32 | serverFollowsRouter.get('/following', | 33 | serverFollowsRouter.get('/following', |
@@ -135,7 +136,6 @@ async function followInstance (req: express.Request, res: express.Response) { | |||
135 | } | 136 | } |
136 | 137 | ||
137 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | 138 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) |
138 | .catch(err => logger.error('Cannot create follow job for %s.', host, err)) | ||
139 | } | 139 | } |
140 | 140 | ||
141 | return res.status(204).end() | 141 | return res.status(204).end() |
@@ -153,7 +153,7 @@ async function removeFollowing (req: express.Request, res: express.Response) { | |||
153 | await server.save({ transaction: t }) | 153 | await server.save({ transaction: t }) |
154 | 154 | ||
155 | // Async, could be long | 155 | // Async, could be long |
156 | removeRedundancyOf(server.id) | 156 | removeRedundanciesOfServer(server.id) |
157 | .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, err)) | 157 | .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, err)) |
158 | 158 | ||
159 | await follow.destroy({ transaction: t }) | 159 | await follow.destroy({ transaction: t }) |
diff --git a/server/controllers/api/server/logs.ts b/server/controllers/api/server/logs.ts index cd1e0f5bf..4b543d686 100644 --- a/server/controllers/api/server/logs.ts +++ b/server/controllers/api/server/logs.ts | |||
@@ -59,9 +59,9 @@ async function getLogs (req: express.Request, res: express.Response) { | |||
59 | } | 59 | } |
60 | 60 | ||
61 | async function generateOutput (options: { | 61 | async function generateOutput (options: { |
62 | startDateQuery: string, | 62 | startDateQuery: string |
63 | endDateQuery?: string, | 63 | endDateQuery?: string |
64 | level: LogLevel, | 64 | level: LogLevel |
65 | nameFilter: RegExp | 65 | nameFilter: RegExp |
66 | }) { | 66 | }) { |
67 | const { startDateQuery, level, nameFilter } = options | 67 | const { startDateQuery, level, nameFilter } = options |
@@ -111,7 +111,7 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date, | |||
111 | const output: any[] = [] | 111 | const output: any[] = [] |
112 | 112 | ||
113 | for (let i = lines.length - 1; i >= 0; i--) { | 113 | for (let i = lines.length - 1; i >= 0; i--) { |
114 | const line = lines[ i ] | 114 | const line = lines[i] |
115 | let log: any | 115 | let log: any |
116 | 116 | ||
117 | try { | 117 | try { |
@@ -122,7 +122,7 @@ async function getOutputFromFile (path: string, startDate: Date, endDate: Date, | |||
122 | } | 122 | } |
123 | 123 | ||
124 | logTime = new Date(log.timestamp).getTime() | 124 | logTime = new Date(log.timestamp).getTime() |
125 | if (logTime >= startTime && logTime <= endTime && logsLevel[ log.level ] >= logsLevel[ level ]) { | 125 | if (logTime >= startTime && logTime <= endTime && logsLevel[log.level] >= logsLevel[level]) { |
126 | output.push(log) | 126 | output.push(log) |
127 | 127 | ||
128 | currentSize += line.length | 128 | currentSize += line.length |
diff --git a/server/controllers/api/server/redundancy.ts b/server/controllers/api/server/redundancy.ts index 4ea6164a3..1ced0759e 100644 --- a/server/controllers/api/server/redundancy.ts +++ b/server/controllers/api/server/redundancy.ts | |||
@@ -1,9 +1,24 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight } from '../../../../shared/models/users' | 2 | import { UserRight } from '../../../../shared/models/users' |
3 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' | 3 | import { |
4 | import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy' | 4 | asyncMiddleware, |
5 | import { removeRedundancyOf } from '../../../lib/redundancy' | 5 | authenticate, |
6 | ensureUserHasRight, | ||
7 | paginationValidator, | ||
8 | setDefaultPagination, | ||
9 | setDefaultVideoRedundanciesSort, | ||
10 | videoRedundanciesSortValidator | ||
11 | } from '../../../middlewares' | ||
12 | import { | ||
13 | listVideoRedundanciesValidator, | ||
14 | updateServerRedundancyValidator, | ||
15 | addVideoRedundancyValidator, | ||
16 | removeVideoRedundancyValidator | ||
17 | } from '../../../middlewares/validators/redundancy' | ||
18 | import { removeRedundanciesOfServer, removeVideoRedundancy } from '../../../lib/redundancy' | ||
6 | import { logger } from '../../../helpers/logger' | 19 | import { logger } from '../../../helpers/logger' |
20 | import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy' | ||
21 | import { JobQueue } from '@server/lib/job-queue' | ||
7 | 22 | ||
8 | const serverRedundancyRouter = express.Router() | 23 | const serverRedundancyRouter = express.Router() |
9 | 24 | ||
@@ -14,6 +29,31 @@ serverRedundancyRouter.put('/redundancy/:host', | |||
14 | asyncMiddleware(updateRedundancy) | 29 | asyncMiddleware(updateRedundancy) |
15 | ) | 30 | ) |
16 | 31 | ||
32 | serverRedundancyRouter.get('/redundancy/videos', | ||
33 | authenticate, | ||
34 | ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES), | ||
35 | listVideoRedundanciesValidator, | ||
36 | paginationValidator, | ||
37 | videoRedundanciesSortValidator, | ||
38 | setDefaultVideoRedundanciesSort, | ||
39 | setDefaultPagination, | ||
40 | asyncMiddleware(listVideoRedundancies) | ||
41 | ) | ||
42 | |||
43 | serverRedundancyRouter.post('/redundancy/videos', | ||
44 | authenticate, | ||
45 | ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES), | ||
46 | addVideoRedundancyValidator, | ||
47 | asyncMiddleware(addVideoRedundancy) | ||
48 | ) | ||
49 | |||
50 | serverRedundancyRouter.delete('/redundancy/videos/:redundancyId', | ||
51 | authenticate, | ||
52 | ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES), | ||
53 | removeVideoRedundancyValidator, | ||
54 | asyncMiddleware(removeVideoRedundancyController) | ||
55 | ) | ||
56 | |||
17 | // --------------------------------------------------------------------------- | 57 | // --------------------------------------------------------------------------- |
18 | 58 | ||
19 | export { | 59 | export { |
@@ -22,6 +62,42 @@ export { | |||
22 | 62 | ||
23 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
24 | 64 | ||
65 | async function listVideoRedundancies (req: express.Request, res: express.Response) { | ||
66 | const resultList = await VideoRedundancyModel.listForApi({ | ||
67 | start: req.query.start, | ||
68 | count: req.query.count, | ||
69 | sort: req.query.sort, | ||
70 | target: req.query.target, | ||
71 | strategy: req.query.strategy | ||
72 | }) | ||
73 | |||
74 | const result = { | ||
75 | total: resultList.total, | ||
76 | data: resultList.data.map(r => VideoRedundancyModel.toFormattedJSONStatic(r)) | ||
77 | } | ||
78 | |||
79 | return res.json(result) | ||
80 | } | ||
81 | |||
82 | async function addVideoRedundancy (req: express.Request, res: express.Response) { | ||
83 | const payload = { | ||
84 | videoId: res.locals.onlyVideo.id | ||
85 | } | ||
86 | |||
87 | await JobQueue.Instance.createJobWithPromise({ | ||
88 | type: 'video-redundancy', | ||
89 | payload | ||
90 | }) | ||
91 | |||
92 | return res.sendStatus(204) | ||
93 | } | ||
94 | |||
95 | async function removeVideoRedundancyController (req: express.Request, res: express.Response) { | ||
96 | await removeVideoRedundancy(res.locals.videoRedundancy) | ||
97 | |||
98 | return res.sendStatus(204) | ||
99 | } | ||
100 | |||
25 | async function updateRedundancy (req: express.Request, res: express.Response) { | 101 | async function updateRedundancy (req: express.Request, res: express.Response) { |
26 | const server = res.locals.server | 102 | const server = res.locals.server |
27 | 103 | ||
@@ -30,7 +106,7 @@ async function updateRedundancy (req: express.Request, res: express.Response) { | |||
30 | await server.save() | 106 | await server.save() |
31 | 107 | ||
32 | // Async, could be long | 108 | // Async, could be long |
33 | removeRedundancyOf(server.id) | 109 | removeRedundanciesOfServer(server.id) |
34 | .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err })) | 110 | .catch(err => logger.error('Cannot remove redundancy of %s.', server.host, { err })) |
35 | 111 | ||
36 | return res.sendStatus(204) | 112 | return res.sendStatus(204) |
diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts index d165db191..f849b15c7 100644 --- a/server/controllers/api/server/server-blocklist.ts +++ b/server/controllers/api/server/server-blocklist.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'multer' | 2 | import 'multer' |
3 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' | 3 | import { getFormattedObjects } from '../../../helpers/utils' |
4 | import { | 4 | import { |
5 | asyncMiddleware, | 5 | asyncMiddleware, |
6 | asyncRetryTransactionMiddleware, | 6 | asyncRetryTransactionMiddleware, |
@@ -22,6 +22,7 @@ import { AccountBlocklistModel } from '../../../models/account/account-blocklist | |||
22 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' | 22 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' |
23 | import { ServerBlocklistModel } from '../../../models/server/server-blocklist' | 23 | import { ServerBlocklistModel } from '../../../models/server/server-blocklist' |
24 | import { UserRight } from '../../../../shared/models/users' | 24 | import { UserRight } from '../../../../shared/models/users' |
25 | import { getServerActor } from '@server/models/application/application' | ||
25 | 26 | ||
26 | const serverBlocklistRouter = express.Router() | 27 | const serverBlocklistRouter = express.Router() |
27 | 28 | ||
@@ -82,7 +83,13 @@ export { | |||
82 | async function listBlockedAccounts (req: express.Request, res: express.Response) { | 83 | async function listBlockedAccounts (req: express.Request, res: express.Response) { |
83 | const serverActor = await getServerActor() | 84 | const serverActor = await getServerActor() |
84 | 85 | ||
85 | const resultList = await AccountBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort) | 86 | const resultList = await AccountBlocklistModel.listForApi({ |
87 | start: req.query.start, | ||
88 | count: req.query.count, | ||
89 | sort: req.query.sort, | ||
90 | search: req.query.search, | ||
91 | accountId: serverActor.Account.id | ||
92 | }) | ||
86 | 93 | ||
87 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 94 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
88 | } | 95 | } |
@@ -107,7 +114,13 @@ async function unblockAccount (req: express.Request, res: express.Response) { | |||
107 | async function listBlockedServers (req: express.Request, res: express.Response) { | 114 | async function listBlockedServers (req: express.Request, res: express.Response) { |
108 | const serverActor = await getServerActor() | 115 | const serverActor = await getServerActor() |
109 | 116 | ||
110 | const resultList = await ServerBlocklistModel.listForApi(serverActor.Account.id, req.query.start, req.query.count, req.query.sort) | 117 | const resultList = await ServerBlocklistModel.listForApi({ |
118 | start: req.query.start, | ||
119 | count: req.query.count, | ||
120 | sort: req.query.sort, | ||
121 | search: req.query.search, | ||
122 | accountId: serverActor.Account.id | ||
123 | }) | ||
111 | 124 | ||
112 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 125 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
113 | } | 126 | } |
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts index 3616c074d..f07301a04 100644 --- a/server/controllers/api/server/stats.ts +++ b/server/controllers/api/server/stats.ts | |||
@@ -10,6 +10,7 @@ import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants' | |||
10 | import { cacheRoute } from '../../../middlewares/cache' | 10 | import { cacheRoute } from '../../../middlewares/cache' |
11 | import { VideoFileModel } from '../../../models/video/video-file' | 11 | import { VideoFileModel } from '../../../models/video/video-file' |
12 | import { CONFIG } from '../../../initializers/config' | 12 | import { CONFIG } from '../../../initializers/config' |
13 | import { VideoRedundancyStrategyWithManual } from '@shared/models' | ||
13 | 14 | ||
14 | const statsRouter = express.Router() | 15 | const statsRouter = express.Router() |
15 | 16 | ||
@@ -21,12 +22,20 @@ statsRouter.get('/stats', | |||
21 | async function getStats (req: express.Request, res: express.Response) { | 22 | async function getStats (req: express.Request, res: express.Response) { |
22 | const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats() | 23 | const { totalLocalVideos, totalLocalVideoViews, totalVideos } = await VideoModel.getStats() |
23 | const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats() | 24 | const { totalLocalVideoComments, totalVideoComments } = await VideoCommentModel.getStats() |
24 | const { totalUsers } = await UserModel.getStats() | 25 | const { totalUsers, totalDailyActiveUsers, totalWeeklyActiveUsers, totalMonthlyActiveUsers } = await UserModel.getStats() |
25 | const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats() | 26 | const { totalInstanceFollowers, totalInstanceFollowing } = await ActorFollowModel.getStats() |
26 | const { totalLocalVideoFilesSize } = await VideoFileModel.getStats() | 27 | const { totalLocalVideoFilesSize } = await VideoFileModel.getStats() |
27 | 28 | ||
29 | const strategies = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES | ||
30 | .map(r => ({ | ||
31 | strategy: r.strategy as VideoRedundancyStrategyWithManual, | ||
32 | size: r.size | ||
33 | })) | ||
34 | |||
35 | strategies.push({ strategy: 'manual', size: null }) | ||
36 | |||
28 | const videosRedundancyStats = await Promise.all( | 37 | const videosRedundancyStats = await Promise.all( |
29 | CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.map(r => { | 38 | strategies.map(r => { |
30 | return VideoRedundancyModel.getStats(r.strategy) | 39 | return VideoRedundancyModel.getStats(r.strategy) |
31 | .then(stats => Object.assign(stats, { strategy: r.strategy, totalSize: r.size })) | 40 | .then(stats => Object.assign(stats, { strategy: r.strategy, totalSize: r.size })) |
32 | }) | 41 | }) |
@@ -39,9 +48,15 @@ async function getStats (req: express.Request, res: express.Response) { | |||
39 | totalLocalVideoComments, | 48 | totalLocalVideoComments, |
40 | totalVideos, | 49 | totalVideos, |
41 | totalVideoComments, | 50 | totalVideoComments, |
51 | |||
42 | totalUsers, | 52 | totalUsers, |
53 | totalDailyActiveUsers, | ||
54 | totalWeeklyActiveUsers, | ||
55 | totalMonthlyActiveUsers, | ||
56 | |||
43 | totalInstanceFollowers, | 57 | totalInstanceFollowers, |
44 | totalInstanceFollowing, | 58 | totalInstanceFollowing, |
59 | |||
45 | videosRedundancy: videosRedundancyStats | 60 | videosRedundancy: videosRedundancyStats |
46 | } | 61 | } |
47 | 62 | ||
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index b960e80c1..c488f720b 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import * as RateLimit from 'express-rate-limit' | 2 | import * as RateLimit from 'express-rate-limit' |
3 | import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' | 3 | import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { getFormattedObjects } from '../../../helpers/utils' | 5 | import { generateRandomString, getFormattedObjects } from '../../../helpers/utils' |
6 | import { WEBSERVER } from '../../../initializers/constants' | 6 | import { WEBSERVER } from '../../../initializers/constants' |
7 | import { Emailer } from '../../../lib/emailer' | 7 | import { Emailer } from '../../../lib/emailer' |
8 | import { Redis } from '../../../lib/redis' | 8 | import { Redis } from '../../../lib/redis' |
@@ -17,7 +17,6 @@ import { | |||
17 | paginationValidator, | 17 | paginationValidator, |
18 | setDefaultPagination, | 18 | setDefaultPagination, |
19 | setDefaultSort, | 19 | setDefaultSort, |
20 | token, | ||
21 | userAutocompleteValidator, | 20 | userAutocompleteValidator, |
22 | usersAddValidator, | 21 | usersAddValidator, |
23 | usersGetValidator, | 22 | usersGetValidator, |
@@ -27,12 +26,12 @@ import { | |||
27 | usersUpdateValidator | 26 | usersUpdateValidator |
28 | } from '../../../middlewares' | 27 | } from '../../../middlewares' |
29 | import { | 28 | import { |
29 | ensureCanManageUser, | ||
30 | usersAskResetPasswordValidator, | 30 | usersAskResetPasswordValidator, |
31 | usersAskSendVerifyEmailValidator, | 31 | usersAskSendVerifyEmailValidator, |
32 | usersBlockingValidator, | 32 | usersBlockingValidator, |
33 | usersResetPasswordValidator, | 33 | usersResetPasswordValidator, |
34 | usersVerifyEmailValidator, | 34 | usersVerifyEmailValidator |
35 | ensureCanManageUser | ||
36 | } from '../../../middlewares/validators' | 35 | } from '../../../middlewares/validators' |
37 | import { UserModel } from '../../../models/account/user' | 36 | import { UserModel } from '../../../models/account/user' |
38 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' | 37 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' |
@@ -50,16 +49,10 @@ import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | |||
50 | import { UserRegister } from '../../../../shared/models/users/user-register.model' | 49 | import { UserRegister } from '../../../../shared/models/users/user-register.model' |
51 | import { MUser, MUserAccountDefault } from '@server/typings/models' | 50 | import { MUser, MUserAccountDefault } from '@server/typings/models' |
52 | import { Hooks } from '@server/lib/plugins/hooks' | 51 | import { Hooks } from '@server/lib/plugins/hooks' |
52 | import { tokensRouter } from '@server/controllers/api/users/token' | ||
53 | 53 | ||
54 | const auditLogger = auditLoggerFactory('users') | 54 | const auditLogger = auditLoggerFactory('users') |
55 | 55 | ||
56 | // FIXME: https://github.com/nfriedly/express-rate-limit/issues/138 | ||
57 | // @ts-ignore | ||
58 | const loginRateLimiter = RateLimit({ | ||
59 | windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, | ||
60 | max: CONFIG.RATES_LIMIT.LOGIN.MAX | ||
61 | }) | ||
62 | |||
63 | // @ts-ignore | 56 | // @ts-ignore |
64 | const signupRateLimiter = RateLimit({ | 57 | const signupRateLimiter = RateLimit({ |
65 | windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, | 58 | windowMs: CONFIG.RATES_LIMIT.SIGNUP.WINDOW_MS, |
@@ -74,6 +67,7 @@ const askSendEmailLimiter = new RateLimit({ | |||
74 | }) | 67 | }) |
75 | 68 | ||
76 | const usersRouter = express.Router() | 69 | const usersRouter = express.Router() |
70 | usersRouter.use('/', tokensRouter) | ||
77 | usersRouter.use('/', myNotificationsRouter) | 71 | usersRouter.use('/', myNotificationsRouter) |
78 | usersRouter.use('/', mySubscriptionsRouter) | 72 | usersRouter.use('/', mySubscriptionsRouter) |
79 | usersRouter.use('/', myBlocklistRouter) | 73 | usersRouter.use('/', myBlocklistRouter) |
@@ -170,13 +164,6 @@ usersRouter.post('/:id/verify-email', | |||
170 | asyncMiddleware(verifyUserEmail) | 164 | asyncMiddleware(verifyUserEmail) |
171 | ) | 165 | ) |
172 | 166 | ||
173 | usersRouter.post('/token', | ||
174 | loginRateLimiter, | ||
175 | token, | ||
176 | tokenSuccess | ||
177 | ) | ||
178 | // TODO: Once https://github.com/oauthjs/node-oauth2-server/pull/289 is merged, implement revoke token route | ||
179 | |||
180 | // --------------------------------------------------------------------------- | 167 | // --------------------------------------------------------------------------- |
181 | 168 | ||
182 | export { | 169 | export { |
@@ -199,11 +186,25 @@ async function createUser (req: express.Request, res: express.Response) { | |||
199 | adminFlags: body.adminFlags || UserAdminFlag.NONE | 186 | adminFlags: body.adminFlags || UserAdminFlag.NONE |
200 | }) as MUser | 187 | }) as MUser |
201 | 188 | ||
189 | // NB: due to the validator usersAddValidator, password==='' can only be true if we can send the mail. | ||
190 | const createPassword = userToCreate.password === '' | ||
191 | if (createPassword) { | ||
192 | userToCreate.password = await generateRandomString(20) | ||
193 | } | ||
194 | |||
202 | const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) | 195 | const { user, account, videoChannel } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) |
203 | 196 | ||
204 | auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) | 197 | auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) |
205 | logger.info('User %s with its channel and account created.', body.username) | 198 | logger.info('User %s with its channel and account created.', body.username) |
206 | 199 | ||
200 | if (createPassword) { | ||
201 | // this will send an email for newly created users, so then can set their first password. | ||
202 | logger.info('Sending to user %s a create password email', body.username) | ||
203 | const verificationString = await Redis.Instance.setCreatePasswordVerificationString(user.id) | ||
204 | const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString | ||
205 | await Emailer.Instance.addPasswordCreateEmailJob(userToCreate.username, user.email, url) | ||
206 | } | ||
207 | |||
207 | Hooks.runAction('action:api.user.created', { body, user, account, videoChannel }) | 208 | Hooks.runAction('action:api.user.created', { body, user, account, videoChannel }) |
208 | 209 | ||
209 | return res.json({ | 210 | return res.json({ |
@@ -369,12 +370,6 @@ async function verifyUserEmail (req: express.Request, res: express.Response) { | |||
369 | return res.status(204).end() | 370 | return res.status(204).end() |
370 | } | 371 | } |
371 | 372 | ||
372 | function tokenSuccess (req: express.Request) { | ||
373 | const username = req.body.username | ||
374 | |||
375 | Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip }) | ||
376 | } | ||
377 | |||
378 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { | 373 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { |
379 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) | 374 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) |
380 | 375 | ||
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index ac7c62aab..23890e20c 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -39,7 +39,7 @@ meRouter.get('/me', | |||
39 | ) | 39 | ) |
40 | meRouter.delete('/me', | 40 | meRouter.delete('/me', |
41 | authenticate, | 41 | authenticate, |
42 | asyncMiddleware(deleteMeValidator), | 42 | deleteMeValidator, |
43 | asyncMiddleware(deleteMe) | 43 | asyncMiddleware(deleteMe) |
44 | ) | 44 | ) |
45 | 45 | ||
@@ -214,7 +214,7 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
214 | } | 214 | } |
215 | 215 | ||
216 | async function updateMyAvatar (req: express.Request, res: express.Response) { | 216 | async function updateMyAvatar (req: express.Request, res: express.Response) { |
217 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] | 217 | const avatarPhysicalFile = req.files['avatarfile'][0] |
218 | const user = res.locals.oauth.token.user | 218 | const user = res.locals.oauth.token.user |
219 | 219 | ||
220 | const userAccount = await AccountModel.load(user.Account.id) | 220 | const userAccount = await AccountModel.load(user.Account.id) |
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts index 713c16022..3a44376f2 100644 --- a/server/controllers/api/users/my-blocklist.ts +++ b/server/controllers/api/users/my-blocklist.ts | |||
@@ -74,7 +74,13 @@ export { | |||
74 | async function listBlockedAccounts (req: express.Request, res: express.Response) { | 74 | async function listBlockedAccounts (req: express.Request, res: express.Response) { |
75 | const user = res.locals.oauth.token.User | 75 | const user = res.locals.oauth.token.User |
76 | 76 | ||
77 | const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort) | 77 | const resultList = await AccountBlocklistModel.listForApi({ |
78 | start: req.query.start, | ||
79 | count: req.query.count, | ||
80 | sort: req.query.sort, | ||
81 | search: req.query.search, | ||
82 | accountId: user.Account.id | ||
83 | }) | ||
78 | 84 | ||
79 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 85 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
80 | } | 86 | } |
@@ -99,7 +105,13 @@ async function unblockAccount (req: express.Request, res: express.Response) { | |||
99 | async function listBlockedServers (req: express.Request, res: express.Response) { | 105 | async function listBlockedServers (req: express.Request, res: express.Response) { |
100 | const user = res.locals.oauth.token.User | 106 | const user = res.locals.oauth.token.User |
101 | 107 | ||
102 | const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort) | 108 | const resultList = await ServerBlocklistModel.listForApi({ |
109 | start: req.query.start, | ||
110 | count: req.query.count, | ||
111 | sort: req.query.sort, | ||
112 | search: req.query.search, | ||
113 | accountId: user.Account.id | ||
114 | }) | ||
103 | 115 | ||
104 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 116 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
105 | } | 117 | } |
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts index 4da1f3496..77a15e5fc 100644 --- a/server/controllers/api/users/my-history.ts +++ b/server/controllers/api/users/my-history.ts | |||
@@ -9,7 +9,7 @@ import { | |||
9 | } from '../../../middlewares' | 9 | } from '../../../middlewares' |
10 | import { getFormattedObjects } from '../../../helpers/utils' | 10 | import { getFormattedObjects } from '../../../helpers/utils' |
11 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | 11 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' |
12 | import { sequelizeTypescript } from '../../../initializers' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
13 | 13 | ||
14 | const myVideosHistoryRouter = express.Router() | 14 | const myVideosHistoryRouter = express.Router() |
15 | 15 | ||
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts index 43c4c37d8..efe1b9bc3 100644 --- a/server/controllers/api/users/my-subscriptions.ts +++ b/server/controllers/api/users/my-subscriptions.ts | |||
@@ -19,7 +19,6 @@ import { buildNSFWFilter, getCountVideos } from '../../../helpers/express-utils' | |||
19 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | 19 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
20 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 20 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
21 | import { JobQueue } from '../../../lib/job-queue' | 21 | import { JobQueue } from '../../../lib/job-queue' |
22 | import { logger } from '../../../helpers/logger' | ||
23 | import { sequelizeTypescript } from '../../../initializers/database' | 22 | import { sequelizeTypescript } from '../../../initializers/database' |
24 | 23 | ||
25 | const mySubscriptionsRouter = express.Router() | 24 | const mySubscriptionsRouter = express.Router() |
@@ -52,7 +51,7 @@ mySubscriptionsRouter.get('/me/subscriptions', | |||
52 | mySubscriptionsRouter.post('/me/subscriptions', | 51 | mySubscriptionsRouter.post('/me/subscriptions', |
53 | authenticate, | 52 | authenticate, |
54 | userSubscriptionAddValidator, | 53 | userSubscriptionAddValidator, |
55 | asyncMiddleware(addUserSubscription) | 54 | addUserSubscription |
56 | ) | 55 | ) |
57 | 56 | ||
58 | mySubscriptionsRouter.get('/me/subscriptions/:uri', | 57 | mySubscriptionsRouter.get('/me/subscriptions/:uri', |
@@ -106,18 +105,18 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons | |||
106 | return res.json(existObject) | 105 | return res.json(existObject) |
107 | } | 106 | } |
108 | 107 | ||
109 | async function addUserSubscription (req: express.Request, res: express.Response) { | 108 | function addUserSubscription (req: express.Request, res: express.Response) { |
110 | const user = res.locals.oauth.token.User | 109 | const user = res.locals.oauth.token.User |
111 | const [ name, host ] = req.body.uri.split('@') | 110 | const [ name, host ] = req.body.uri.split('@') |
112 | 111 | ||
113 | const payload = { | 112 | const payload = { |
114 | name, | 113 | name, |
115 | host, | 114 | host, |
115 | assertIsChannel: true, | ||
116 | followerActorId: user.Account.Actor.id | 116 | followerActorId: user.Account.Actor.id |
117 | } | 117 | } |
118 | 118 | ||
119 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | 119 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) |
120 | .catch(err => logger.error('Cannot create follow job for subscription %s.', req.body.uri, err)) | ||
121 | 120 | ||
122 | return res.status(204).end() | 121 | return res.status(204).end() |
123 | } | 122 | } |
diff --git a/server/controllers/api/users/token.ts b/server/controllers/api/users/token.ts new file mode 100644 index 000000000..41aa26769 --- /dev/null +++ b/server/controllers/api/users/token.ts | |||
@@ -0,0 +1,37 @@ | |||
1 | import { handleLogin, handleTokenRevocation } from '@server/lib/auth' | ||
2 | import * as RateLimit from 'express-rate-limit' | ||
3 | import { CONFIG } from '@server/initializers/config' | ||
4 | import * as express from 'express' | ||
5 | import { Hooks } from '@server/lib/plugins/hooks' | ||
6 | import { asyncMiddleware, authenticate } from '@server/middlewares' | ||
7 | |||
8 | const tokensRouter = express.Router() | ||
9 | |||
10 | const loginRateLimiter = RateLimit({ | ||
11 | windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, | ||
12 | max: CONFIG.RATES_LIMIT.LOGIN.MAX | ||
13 | }) | ||
14 | |||
15 | tokensRouter.post('/token', | ||
16 | loginRateLimiter, | ||
17 | handleLogin, | ||
18 | tokenSuccess | ||
19 | ) | ||
20 | |||
21 | tokensRouter.post('/revoke-token', | ||
22 | authenticate, | ||
23 | asyncMiddleware(handleTokenRevocation) | ||
24 | ) | ||
25 | |||
26 | // --------------------------------------------------------------------------- | ||
27 | |||
28 | export { | ||
29 | tokensRouter | ||
30 | } | ||
31 | // --------------------------------------------------------------------------- | ||
32 | |||
33 | function tokenSuccess (req: express.Request) { | ||
34 | const username = req.body.username | ||
35 | |||
36 | Hooks.runAction('action:api.user.oauth2-got-token', { username, ip: req.ip }) | ||
37 | } | ||
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index e1f37a8fb..d779f1aab 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' | 2 | import { getFormattedObjects } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 5 | asyncRetryTransactionMiddleware, |
@@ -21,7 +21,7 @@ import { sendUpdateActor } from '../../lib/activitypub/send' | |||
21 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' | 21 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' |
22 | import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' | 22 | import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' |
23 | import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 23 | import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
24 | import { setAsyncActorKeys } from '../../lib/activitypub' | 24 | import { setAsyncActorKeys } from '../../lib/activitypub/actor' |
25 | import { AccountModel } from '../../models/account/account' | 25 | import { AccountModel } from '../../models/account/account' |
26 | import { MIMETYPES } from '../../initializers/constants' | 26 | import { MIMETYPES } from '../../initializers/constants' |
27 | import { logger } from '../../helpers/logger' | 27 | import { logger } from '../../helpers/logger' |
@@ -36,6 +36,7 @@ import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validator | |||
36 | import { CONFIG } from '../../initializers/config' | 36 | import { CONFIG } from '../../initializers/config' |
37 | import { sequelizeTypescript } from '../../initializers/database' | 37 | import { sequelizeTypescript } from '../../initializers/database' |
38 | import { MChannelAccountDefault } from '@server/typings/models' | 38 | import { MChannelAccountDefault } from '@server/typings/models' |
39 | import { getServerActor } from '@server/models/application/application' | ||
39 | 40 | ||
40 | const auditLogger = auditLoggerFactory('channels') | 41 | const auditLogger = auditLoggerFactory('channels') |
41 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 42 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) |
@@ -119,7 +120,7 @@ async function listVideoChannels (req: express.Request, res: express.Response) { | |||
119 | } | 120 | } |
120 | 121 | ||
121 | async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { | 122 | async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { |
122 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] | 123 | const avatarPhysicalFile = req.files['avatarfile'][0] |
123 | const videoChannel = res.locals.videoChannel | 124 | const videoChannel = res.locals.videoChannel |
124 | const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) | 125 | const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) |
125 | 126 | ||
@@ -232,7 +233,6 @@ async function getVideoChannel (req: express.Request, res: express.Response) { | |||
232 | 233 | ||
233 | if (videoChannelWithVideos.isOutdated()) { | 234 | if (videoChannelWithVideos.isOutdated()) { |
234 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } }) | 235 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: videoChannelWithVideos.Actor.url } }) |
235 | .catch(err => logger.error('Cannot create AP refresher job for actor %s.', videoChannelWithVideos.Actor.url, { err })) | ||
236 | } | 236 | } |
237 | 237 | ||
238 | return res.json(videoChannelWithVideos.toFormattedJSON()) | 238 | return res.json(videoChannelWithVideos.toFormattedJSON()) |
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index d9f0ff925..375d711fd 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' | 2 | import { getFormattedObjects } from '../../helpers/utils' |
3 | import { | 3 | import { |
4 | asyncMiddleware, | 4 | asyncMiddleware, |
5 | asyncRetryTransactionMiddleware, | 5 | asyncRetryTransactionMiddleware, |
@@ -41,6 +41,7 @@ import { CONFIG } from '../../initializers/config' | |||
41 | import { sequelizeTypescript } from '../../initializers/database' | 41 | import { sequelizeTypescript } from '../../initializers/database' |
42 | import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' | 42 | import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' |
43 | import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models' | 43 | import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models' |
44 | import { getServerActor } from '@server/models/application/application' | ||
44 | 45 | ||
45 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) | 46 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) |
46 | 47 | ||
@@ -144,7 +145,6 @@ function getVideoPlaylist (req: express.Request, res: express.Response) { | |||
144 | 145 | ||
145 | if (videoPlaylist.isOutdated()) { | 146 | if (videoPlaylist.isOutdated()) { |
146 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) | 147 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) |
147 | .catch(err => logger.error('Cannot create AP refresher job for playlist %s.', videoPlaylist.url, { err })) | ||
148 | } | 148 | } |
149 | 149 | ||
150 | return res.json(videoPlaylist.toFormattedJSON()) | 150 | return res.json(videoPlaylist.toFormattedJSON()) |
@@ -464,7 +464,13 @@ async function regeneratePlaylistThumbnail (videoPlaylist: MVideoPlaylistThumbna | |||
464 | async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) { | 464 | async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) { |
465 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) | 465 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) |
466 | 466 | ||
467 | const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename) | 467 | const videoMiniature = video.getMiniature() |
468 | if (!videoMiniature) { | ||
469 | logger.info('Cannot generate thumbnail for playlist %s because video %s does not have any.', videoPlaylist.url, video.url) | ||
470 | return | ||
471 | } | ||
472 | |||
473 | const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, videoMiniature.filename) | ||
468 | const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true, true) | 474 | const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true, true) |
469 | 475 | ||
470 | thumbnailModel.videoPlaylistId = videoPlaylist.id | 476 | thumbnailModel.videoPlaylistId = videoPlaylist.id |
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 4ae899b7e..2af7b3864 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' | 2 | import { UserRight, VideoAbuseCreate, VideoAbuseState, VideoAbuse } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers/database' |
6 | import { | 6 | import { |
7 | asyncMiddleware, | 7 | asyncMiddleware, |
8 | asyncRetryTransactionMiddleware, | 8 | asyncRetryTransactionMiddleware, |
@@ -14,7 +14,8 @@ import { | |||
14 | videoAbuseGetValidator, | 14 | videoAbuseGetValidator, |
15 | videoAbuseReportValidator, | 15 | videoAbuseReportValidator, |
16 | videoAbusesSortValidator, | 16 | videoAbusesSortValidator, |
17 | videoAbuseUpdateValidator | 17 | videoAbuseUpdateValidator, |
18 | videoAbuseListValidator | ||
18 | } from '../../../middlewares' | 19 | } from '../../../middlewares' |
19 | import { AccountModel } from '../../../models/account/account' | 20 | import { AccountModel } from '../../../models/account/account' |
20 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 21 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
@@ -22,6 +23,8 @@ import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit- | |||
22 | import { Notifier } from '../../../lib/notifier' | 23 | import { Notifier } from '../../../lib/notifier' |
23 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' | 24 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' |
24 | import { MVideoAbuseAccountVideo } from '../../../typings/models/video' | 25 | import { MVideoAbuseAccountVideo } from '../../../typings/models/video' |
26 | import { getServerActor } from '@server/models/application/application' | ||
27 | import { MAccountDefault } from '@server/typings/models' | ||
25 | 28 | ||
26 | const auditLogger = auditLoggerFactory('abuse') | 29 | const auditLogger = auditLoggerFactory('abuse') |
27 | const abuseVideoRouter = express.Router() | 30 | const abuseVideoRouter = express.Router() |
@@ -33,6 +36,7 @@ abuseVideoRouter.get('/abuse', | |||
33 | videoAbusesSortValidator, | 36 | videoAbusesSortValidator, |
34 | setDefaultSort, | 37 | setDefaultSort, |
35 | setDefaultPagination, | 38 | setDefaultPagination, |
39 | videoAbuseListValidator, | ||
36 | asyncMiddleware(listVideoAbuses) | 40 | asyncMiddleware(listVideoAbuses) |
37 | ) | 41 | ) |
38 | abuseVideoRouter.put('/:videoId/abuse/:id', | 42 | abuseVideoRouter.put('/:videoId/abuse/:id', |
@@ -69,6 +73,14 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { | |||
69 | start: req.query.start, | 73 | start: req.query.start, |
70 | count: req.query.count, | 74 | count: req.query.count, |
71 | sort: req.query.sort, | 75 | sort: req.query.sort, |
76 | id: req.query.id, | ||
77 | search: req.query.search, | ||
78 | state: req.query.state, | ||
79 | videoIs: req.query.videoIs, | ||
80 | searchReporter: req.query.searchReporter, | ||
81 | searchReportee: req.query.searchReportee, | ||
82 | searchVideo: req.query.searchVideo, | ||
83 | searchVideoChannel: req.query.searchVideoChannel, | ||
72 | serverAccountId: serverActor.Account.id, | 84 | serverAccountId: serverActor.Account.id, |
73 | user | 85 | user |
74 | }) | 86 | }) |
@@ -106,9 +118,11 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) { | |||
106 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 118 | async function reportVideoAbuse (req: express.Request, res: express.Response) { |
107 | const videoInstance = res.locals.videoAll | 119 | const videoInstance = res.locals.videoAll |
108 | const body: VideoAbuseCreate = req.body | 120 | const body: VideoAbuseCreate = req.body |
121 | let reporterAccount: MAccountDefault | ||
122 | let videoAbuseJSON: VideoAbuse | ||
109 | 123 | ||
110 | const videoAbuse = await sequelizeTypescript.transaction(async t => { | 124 | const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { |
111 | const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | 125 | reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) |
112 | 126 | ||
113 | const abuseToCreate = { | 127 | const abuseToCreate = { |
114 | reporterAccountId: reporterAccount.id, | 128 | reporterAccountId: reporterAccount.id, |
@@ -126,14 +140,19 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { | |||
126 | await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) | 140 | await sendVideoAbuse(reporterAccount.Actor, videoAbuseInstance, videoInstance, t) |
127 | } | 141 | } |
128 | 142 | ||
129 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseInstance.toFormattedJSON())) | 143 | videoAbuseJSON = videoAbuseInstance.toFormattedJSON() |
144 | auditLogger.create(reporterAccount.Actor.getIdentifier(), new VideoAbuseAuditView(videoAbuseJSON)) | ||
130 | 145 | ||
131 | return videoAbuseInstance | 146 | return videoAbuseInstance |
132 | }) | 147 | }) |
133 | 148 | ||
134 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) | 149 | Notifier.Instance.notifyOnNewVideoAbuse({ |
150 | videoAbuse: videoAbuseJSON, | ||
151 | videoAbuseInstance, | ||
152 | reporter: reporterAccount.Actor.getIdentifier() | ||
153 | }) | ||
135 | 154 | ||
136 | logger.info('Abuse report for video %s created.', videoInstance.name) | 155 | logger.info('Abuse report for video %s created.', videoInstance.name) |
137 | 156 | ||
138 | return res.json({ videoAbuse: videoAbuse.toFormattedJSON() }).end() | 157 | return res.json({ videoAbuse: videoAbuseJSON }).end() |
139 | } | 158 | } |
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 2a667480d..3b25ceea2 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' | 2 | import { blacklistVideo, unblacklistVideo } from '@server/lib/video-blacklist' |
3 | import { UserRight, VideoBlacklistCreate } from '../../../../shared' | ||
3 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 5 | import { getFormattedObjects } from '../../../helpers/utils' |
6 | import { sequelizeTypescript } from '../../../initializers/database' | ||
5 | import { | 7 | import { |
6 | asyncMiddleware, | 8 | asyncMiddleware, |
7 | authenticate, | 9 | authenticate, |
@@ -16,11 +18,6 @@ import { | |||
16 | videosBlacklistUpdateValidator | 18 | videosBlacklistUpdateValidator |
17 | } from '../../../middlewares' | 19 | } from '../../../middlewares' |
18 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | 20 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' |
19 | import { sequelizeTypescript } from '../../../initializers' | ||
20 | import { Notifier } from '../../../lib/notifier' | ||
21 | import { sendDeleteVideo } from '../../../lib/activitypub/send' | ||
22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' | ||
23 | import { MVideoBlacklistVideo } from '@server/typings/models' | ||
24 | 21 | ||
25 | const blacklistRouter = express.Router() | 22 | const blacklistRouter = express.Router() |
26 | 23 | ||
@@ -28,7 +25,7 @@ blacklistRouter.post('/:videoId/blacklist', | |||
28 | authenticate, | 25 | authenticate, |
29 | ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST), | 26 | ensureUserHasRight(UserRight.MANAGE_VIDEO_BLACKLIST), |
30 | asyncMiddleware(videosBlacklistAddValidator), | 27 | asyncMiddleware(videosBlacklistAddValidator), |
31 | asyncMiddleware(addVideoToBlacklist) | 28 | asyncMiddleware(addVideoToBlacklistController) |
32 | ) | 29 | ) |
33 | 30 | ||
34 | blacklistRouter.get('/blacklist', | 31 | blacklistRouter.get('/blacklist', |
@@ -64,29 +61,15 @@ export { | |||
64 | 61 | ||
65 | // --------------------------------------------------------------------------- | 62 | // --------------------------------------------------------------------------- |
66 | 63 | ||
67 | async function addVideoToBlacklist (req: express.Request, res: express.Response) { | 64 | async function addVideoToBlacklistController (req: express.Request, res: express.Response) { |
68 | const videoInstance = res.locals.videoAll | 65 | const videoInstance = res.locals.videoAll |
69 | const body: VideoBlacklistCreate = req.body | 66 | const body: VideoBlacklistCreate = req.body |
70 | 67 | ||
71 | const toCreate = { | 68 | await blacklistVideo(videoInstance, body) |
72 | videoId: videoInstance.id, | ||
73 | unfederated: body.unfederate === true, | ||
74 | reason: body.reason, | ||
75 | type: VideoBlacklistType.MANUAL | ||
76 | } | ||
77 | |||
78 | const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create(toCreate) | ||
79 | blacklist.Video = videoInstance | ||
80 | |||
81 | if (body.unfederate === true) { | ||
82 | await sendDeleteVideo(videoInstance, undefined) | ||
83 | } | ||
84 | |||
85 | Notifier.Instance.notifyOnVideoBlacklist(blacklist) | ||
86 | 69 | ||
87 | logger.info('Video %s blacklisted.', videoInstance.uuid) | 70 | logger.info('Video %s blacklisted.', videoInstance.uuid) |
88 | 71 | ||
89 | return res.type('json').status(204).end() | 72 | return res.type('json').sendStatus(204) |
90 | } | 73 | } |
91 | 74 | ||
92 | async function updateVideoBlacklistController (req: express.Request, res: express.Response) { | 75 | async function updateVideoBlacklistController (req: express.Request, res: express.Response) { |
@@ -98,11 +81,17 @@ async function updateVideoBlacklistController (req: express.Request, res: expres | |||
98 | return videoBlacklist.save({ transaction: t }) | 81 | return videoBlacklist.save({ transaction: t }) |
99 | }) | 82 | }) |
100 | 83 | ||
101 | return res.type('json').status(204).end() | 84 | return res.type('json').sendStatus(204) |
102 | } | 85 | } |
103 | 86 | ||
104 | async function listBlacklist (req: express.Request, res: express.Response) { | 87 | async function listBlacklist (req: express.Request, res: express.Response) { |
105 | const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.type) | 88 | const resultList = await VideoBlacklistModel.listForApi({ |
89 | start: req.query.start, | ||
90 | count: req.query.count, | ||
91 | sort: req.query.sort, | ||
92 | search: req.query.search, | ||
93 | type: req.query.type | ||
94 | }) | ||
106 | 95 | ||
107 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 96 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
108 | } | 97 | } |
@@ -111,32 +100,9 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex | |||
111 | const videoBlacklist = res.locals.videoBlacklist | 100 | const videoBlacklist = res.locals.videoBlacklist |
112 | const video = res.locals.videoAll | 101 | const video = res.locals.videoAll |
113 | 102 | ||
114 | const videoBlacklistType = await sequelizeTypescript.transaction(async t => { | 103 | await unblacklistVideo(videoBlacklist, video) |
115 | const unfederated = videoBlacklist.unfederated | ||
116 | const videoBlacklistType = videoBlacklist.type | ||
117 | |||
118 | await videoBlacklist.destroy({ transaction: t }) | ||
119 | video.VideoBlacklist = undefined | ||
120 | |||
121 | // Re federate the video | ||
122 | if (unfederated === true) { | ||
123 | await federateVideoIfNeeded(video, true, t) | ||
124 | } | ||
125 | |||
126 | return videoBlacklistType | ||
127 | }) | ||
128 | |||
129 | Notifier.Instance.notifyOnVideoUnblacklist(video) | ||
130 | |||
131 | if (videoBlacklistType === VideoBlacklistType.AUTO_BEFORE_PUBLISHED) { | ||
132 | Notifier.Instance.notifyOnVideoPublishedAfterRemovedFromAutoBlacklist(video) | ||
133 | |||
134 | // Delete on object so new video notifications will send | ||
135 | delete video.VideoBlacklist | ||
136 | Notifier.Instance.notifyOnNewVideoIfNeeded(video) | ||
137 | } | ||
138 | 104 | ||
139 | logger.info('Video %s removed from blacklist.', video.uuid) | 105 | logger.info('Video %s removed from blacklist.', video.uuid) |
140 | 106 | ||
141 | return res.type('json').status(204).end() | 107 | return res.type('json').sendStatus(204) |
142 | } | 108 | } |
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts index 37481d12f..8c1d12ca8 100644 --- a/server/controllers/api/videos/captions.ts +++ b/server/controllers/api/videos/captions.ts | |||
@@ -6,7 +6,7 @@ import { MIMETYPES } from '../../../initializers/constants' | |||
6 | import { getFormattedObjects } from '../../../helpers/utils' | 6 | import { getFormattedObjects } from '../../../helpers/utils' |
7 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 7 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
8 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
9 | import { federateVideoIfNeeded } from '../../../lib/activitypub' | 9 | import { federateVideoIfNeeded } from '../../../lib/activitypub/videos' |
10 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | 10 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' |
11 | import { CONFIG } from '../../../initializers/config' | 11 | import { CONFIG } from '../../../initializers/config' |
12 | import { sequelizeTypescript } from '../../../initializers/database' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
@@ -66,7 +66,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) { | |||
66 | await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption) | 66 | await moveAndProcessCaptionFile(videoCaptionPhysicalFile, videoCaption) |
67 | 67 | ||
68 | await sequelizeTypescript.transaction(async t => { | 68 | await sequelizeTypescript.transaction(async t => { |
69 | await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, t) | 69 | await VideoCaptionModel.insertOrReplaceLanguage(video.id, req.params.captionLanguage, null, t) |
70 | 70 | ||
71 | // Update video update | 71 | // Update video update |
72 | await federateVideoIfNeeded(video, false, t) | 72 | await federateVideoIfNeeded(video, false, t) |
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index 5f3fed5c0..5070bb3c0 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts | |||
@@ -4,7 +4,7 @@ import { ResultList } from '../../../../shared/models' | |||
4 | import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' | 4 | import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { getFormattedObjects } from '../../../helpers/utils' | 6 | import { getFormattedObjects } from '../../../helpers/utils' |
7 | import { sequelizeTypescript } from '../../../initializers' | 7 | import { sequelizeTypescript } from '../../../initializers/database' |
8 | import { buildFormattedCommentTree, createVideoComment, markCommentAsDeleted } from '../../../lib/video-comment' | 8 | import { buildFormattedCommentTree, createVideoComment, markCommentAsDeleted } from '../../../lib/video-comment' |
9 | import { | 9 | import { |
10 | asyncMiddleware, | 10 | asyncMiddleware, |
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 28ced5836..b4f70a086 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -3,12 +3,14 @@ import * as magnetUtil from 'magnet-uri' | |||
3 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' | 3 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' |
4 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | 4 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' |
5 | import { MIMETYPES } from '../../../initializers/constants' | 5 | import { MIMETYPES } from '../../../initializers/constants' |
6 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' | 6 | import { getYoutubeDLInfo, YoutubeDLInfo, getYoutubeDLSubs } from '../../../helpers/youtube-dl' |
7 | import { createReqFiles } from '../../../helpers/express-utils' | 7 | import { createReqFiles } from '../../../helpers/express-utils' |
8 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
9 | import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' | 9 | import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' |
10 | import { VideoModel } from '../../../models/video/video' | 10 | import { VideoModel } from '../../../models/video/video' |
11 | import { getVideoActivityPubUrl } from '../../../lib/activitypub' | 11 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
12 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | ||
13 | import { getVideoActivityPubUrl } from '../../../lib/activitypub/url' | ||
12 | import { TagModel } from '../../../models/video/tag' | 14 | import { TagModel } from '../../../models/video/tag' |
13 | import { VideoImportModel } from '../../../models/video/video-import' | 15 | import { VideoImportModel } from '../../../models/video/video-import' |
14 | import { JobQueue } from '../../../lib/job-queue/job-queue' | 16 | import { JobQueue } from '../../../lib/job-queue/job-queue' |
@@ -21,13 +23,14 @@ import { move, readFile } from 'fs-extra' | |||
21 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | 23 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' |
22 | import { CONFIG } from '../../../initializers/config' | 24 | import { CONFIG } from '../../../initializers/config' |
23 | import { sequelizeTypescript } from '../../../initializers/database' | 25 | import { sequelizeTypescript } from '../../../initializers/database' |
24 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' | 26 | import { createVideoMiniatureFromExisting, createVideoMiniatureFromUrl } from '../../../lib/thumbnail' |
25 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 27 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
26 | import { | 28 | import { |
27 | MChannelAccountDefault, | 29 | MChannelAccountDefault, |
28 | MThumbnail, | 30 | MThumbnail, |
29 | MUser, | 31 | MUser, |
30 | MVideoAccountDefault, | 32 | MVideoAccountDefault, |
33 | MVideoCaptionVideo, | ||
31 | MVideoTag, | 34 | MVideoTag, |
32 | MVideoThumbnailAccountDefault, | 35 | MVideoThumbnailAccountDefault, |
33 | MVideoWithBlacklistLight | 36 | MVideoWithBlacklistLight |
@@ -88,12 +91,12 @@ async function addTorrentImport (req: express.Request, res: express.Response, to | |||
88 | const buf = await readFile(torrentfile.path) | 91 | const buf = await readFile(torrentfile.path) |
89 | const parsedTorrent = parseTorrent(buf) | 92 | const parsedTorrent = parseTorrent(buf) |
90 | 93 | ||
91 | videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[ 0 ] : parsedTorrent.name as string | 94 | videoName = isArray(parsedTorrent.name) ? parsedTorrent.name[0] : parsedTorrent.name as string |
92 | } else { | 95 | } else { |
93 | magnetUri = body.magnetUri | 96 | magnetUri = body.magnetUri |
94 | 97 | ||
95 | const parsed = magnetUtil.decode(magnetUri) | 98 | const parsed = magnetUtil.decode(magnetUri) |
96 | videoName = isArray(parsed.name) ? parsed.name[ 0 ] : parsed.name as string | 99 | videoName = isArray(parsed.name) ? parsed.name[0] : parsed.name as string |
97 | } | 100 | } |
98 | 101 | ||
99 | const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) | 102 | const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) |
@@ -124,7 +127,7 @@ async function addTorrentImport (req: express.Request, res: express.Response, to | |||
124 | videoImportId: videoImport.id, | 127 | videoImportId: videoImport.id, |
125 | magnetUri | 128 | magnetUri |
126 | } | 129 | } |
127 | await JobQueue.Instance.createJob({ type: 'video-import', payload }) | 130 | await JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload }) |
128 | 131 | ||
129 | auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON())) | 132 | auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON())) |
130 | 133 | ||
@@ -136,6 +139,7 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) | |||
136 | const targetUrl = body.targetUrl | 139 | const targetUrl = body.targetUrl |
137 | const user = res.locals.oauth.token.User | 140 | const user = res.locals.oauth.token.User |
138 | 141 | ||
142 | // Get video infos | ||
139 | let youtubeDLInfo: YoutubeDLInfo | 143 | let youtubeDLInfo: YoutubeDLInfo |
140 | try { | 144 | try { |
141 | youtubeDLInfo = await getYoutubeDLInfo(targetUrl) | 145 | youtubeDLInfo = await getYoutubeDLInfo(targetUrl) |
@@ -149,8 +153,25 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) | |||
149 | 153 | ||
150 | const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) | 154 | const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) |
151 | 155 | ||
152 | const thumbnailModel = await processThumbnail(req, video) | 156 | let thumbnailModel: MThumbnail |
153 | const previewModel = await processPreview(req, video) | 157 | |
158 | // Process video thumbnail from request.files | ||
159 | thumbnailModel = await processThumbnail(req, video) | ||
160 | |||
161 | // Process video thumbnail from url if processing from request.files failed | ||
162 | if (!thumbnailModel && youtubeDLInfo.thumbnailUrl) { | ||
163 | thumbnailModel = await processThumbnailFromUrl(youtubeDLInfo.thumbnailUrl, video) | ||
164 | } | ||
165 | |||
166 | let previewModel: MThumbnail | ||
167 | |||
168 | // Process video preview from request.files | ||
169 | previewModel = await processPreview(req, video) | ||
170 | |||
171 | // Process video preview from url if processing from request.files failed | ||
172 | if (!previewModel && youtubeDLInfo.thumbnailUrl) { | ||
173 | previewModel = await processPreviewFromUrl(youtubeDLInfo.thumbnailUrl, video) | ||
174 | } | ||
154 | 175 | ||
155 | const tags = body.tags || youtubeDLInfo.tags | 176 | const tags = body.tags || youtubeDLInfo.tags |
156 | const videoImportAttributes = { | 177 | const videoImportAttributes = { |
@@ -168,15 +189,41 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response) | |||
168 | user | 189 | user |
169 | }) | 190 | }) |
170 | 191 | ||
192 | // Get video subtitles | ||
193 | try { | ||
194 | const subtitles = await getYoutubeDLSubs(targetUrl) | ||
195 | |||
196 | logger.info('Will create %s subtitles from youtube import %s.', subtitles.length, targetUrl) | ||
197 | |||
198 | for (const subtitle of subtitles) { | ||
199 | const videoCaption = new VideoCaptionModel({ | ||
200 | videoId: video.id, | ||
201 | language: subtitle.language | ||
202 | }) as MVideoCaptionVideo | ||
203 | videoCaption.Video = video | ||
204 | |||
205 | // Move physical file | ||
206 | await moveAndProcessCaptionFile(subtitle, videoCaption) | ||
207 | |||
208 | await sequelizeTypescript.transaction(async t => { | ||
209 | await VideoCaptionModel.insertOrReplaceLanguage(video.id, subtitle.language, null, t) | ||
210 | }) | ||
211 | } | ||
212 | } catch (err) { | ||
213 | logger.warn('Cannot get video subtitles.', { err }) | ||
214 | } | ||
215 | |||
171 | // Create job to import the video | 216 | // Create job to import the video |
172 | const payload = { | 217 | const payload = { |
173 | type: 'youtube-dl' as 'youtube-dl', | 218 | type: 'youtube-dl' as 'youtube-dl', |
174 | videoImportId: videoImport.id, | 219 | videoImportId: videoImport.id, |
175 | thumbnailUrl: youtubeDLInfo.thumbnailUrl, | 220 | generateThumbnail: !thumbnailModel, |
176 | downloadThumbnail: !thumbnailModel, | 221 | generatePreview: !previewModel, |
177 | downloadPreview: !previewModel | 222 | fileExt: youtubeDLInfo.fileExt |
223 | ? `.${youtubeDLInfo.fileExt}` | ||
224 | : '.mp4' | ||
178 | } | 225 | } |
179 | await JobQueue.Instance.createJob({ type: 'video-import', payload }) | 226 | await JobQueue.Instance.createJobWithPromise({ type: 'video-import', payload }) |
180 | 227 | ||
181 | auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON())) | 228 | auditLogger.create(getAuditIdFromRes(res), new VideoImportAuditView(videoImport.toFormattedJSON())) |
182 | 229 | ||
@@ -189,7 +236,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You | |||
189 | remote: false, | 236 | remote: false, |
190 | category: body.category || importData.category, | 237 | category: body.category || importData.category, |
191 | licence: body.licence || importData.licence, | 238 | licence: body.licence || importData.licence, |
192 | language: body.language || undefined, | 239 | language: body.language || importData.language, |
193 | commentsEnabled: body.commentsEnabled !== false, // If the value is not "false", the default is "true" | 240 | commentsEnabled: body.commentsEnabled !== false, // If the value is not "false", the default is "true" |
194 | downloadEnabled: body.downloadEnabled !== false, | 241 | downloadEnabled: body.downloadEnabled !== false, |
195 | waitTranscoding: body.waitTranscoding || false, | 242 | waitTranscoding: body.waitTranscoding || false, |
@@ -200,7 +247,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You | |||
200 | privacy: body.privacy || VideoPrivacy.PRIVATE, | 247 | privacy: body.privacy || VideoPrivacy.PRIVATE, |
201 | duration: 0, // duration will be set by the import job | 248 | duration: 0, // duration will be set by the import job |
202 | channelId: channelId, | 249 | channelId: channelId, |
203 | originallyPublishedAt: importData.originallyPublishedAt | 250 | originallyPublishedAt: body.originallyPublishedAt || importData.originallyPublishedAt |
204 | } | 251 | } |
205 | const video = new VideoModel(videoData) | 252 | const video = new VideoModel(videoData) |
206 | video.url = getVideoActivityPubUrl(video) | 253 | video.url = getVideoActivityPubUrl(video) |
@@ -211,7 +258,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You | |||
211 | async function processThumbnail (req: express.Request, video: VideoModel) { | 258 | async function processThumbnail (req: express.Request, video: VideoModel) { |
212 | const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined | 259 | const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined |
213 | if (thumbnailField) { | 260 | if (thumbnailField) { |
214 | const thumbnailPhysicalFile = thumbnailField[ 0 ] | 261 | const thumbnailPhysicalFile = thumbnailField[0] |
215 | 262 | ||
216 | return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE, false) | 263 | return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE, false) |
217 | } | 264 | } |
@@ -230,13 +277,31 @@ async function processPreview (req: express.Request, video: VideoModel) { | |||
230 | return undefined | 277 | return undefined |
231 | } | 278 | } |
232 | 279 | ||
280 | async function processThumbnailFromUrl (url: string, video: VideoModel) { | ||
281 | try { | ||
282 | return createVideoMiniatureFromUrl(url, video, ThumbnailType.MINIATURE) | ||
283 | } catch (err) { | ||
284 | logger.warn('Cannot generate video thumbnail %s for %s.', url, video.url, { err }) | ||
285 | return undefined | ||
286 | } | ||
287 | } | ||
288 | |||
289 | async function processPreviewFromUrl (url: string, video: VideoModel) { | ||
290 | try { | ||
291 | return createVideoMiniatureFromUrl(url, video, ThumbnailType.PREVIEW) | ||
292 | } catch (err) { | ||
293 | logger.warn('Cannot generate video preview %s for %s.', url, video.url, { err }) | ||
294 | return undefined | ||
295 | } | ||
296 | } | ||
297 | |||
233 | function insertIntoDB (parameters: { | 298 | function insertIntoDB (parameters: { |
234 | video: MVideoThumbnailAccountDefault, | 299 | video: MVideoThumbnailAccountDefault |
235 | thumbnailModel: MThumbnail, | 300 | thumbnailModel: MThumbnail |
236 | previewModel: MThumbnail, | 301 | previewModel: MThumbnail |
237 | videoChannel: MChannelAccountDefault, | 302 | videoChannel: MChannelAccountDefault |
238 | tags: string[], | 303 | tags: string[] |
239 | videoImportAttributes: Partial<MVideoImport>, | 304 | videoImportAttributes: Partial<MVideoImport> |
240 | user: MUser | 305 | user: MUser |
241 | }): Bluebird<MVideoImportFormattable> { | 306 | }): Bluebird<MVideoImportFormattable> { |
242 | const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters | 307 | const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 8d4ff07eb..8048c568c 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { extname } from 'path' | 2 | import { extname } from 'path' |
3 | import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' | 3 | import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' |
4 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 4 | import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 6 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
7 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' | 7 | import { getFormattedObjects } from '../../../helpers/utils' |
8 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | 8 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' |
9 | import { | 9 | import { |
10 | DEFAULT_AUDIO_RESOLUTION, | 10 | DEFAULT_AUDIO_RESOLUTION, |
@@ -14,12 +14,7 @@ import { | |||
14 | VIDEO_LICENCES, | 14 | VIDEO_LICENCES, |
15 | VIDEO_PRIVACIES | 15 | VIDEO_PRIVACIES |
16 | } from '../../../initializers/constants' | 16 | } from '../../../initializers/constants' |
17 | import { | 17 | import { federateVideoIfNeeded, fetchRemoteVideoDescription } from '../../../lib/activitypub/videos' |
18 | changeVideoChannelShare, | ||
19 | federateVideoIfNeeded, | ||
20 | fetchRemoteVideoDescription, | ||
21 | getVideoActivityPubUrl | ||
22 | } from '../../../lib/activitypub' | ||
23 | import { JobQueue } from '../../../lib/job-queue' | 18 | import { JobQueue } from '../../../lib/job-queue' |
24 | import { Redis } from '../../../lib/redis' | 19 | import { Redis } from '../../../lib/redis' |
25 | import { | 20 | import { |
@@ -32,6 +27,7 @@ import { | |||
32 | paginationValidator, | 27 | paginationValidator, |
33 | setDefaultPagination, | 28 | setDefaultPagination, |
34 | setDefaultSort, | 29 | setDefaultSort, |
30 | videoFileMetadataGetValidator, | ||
35 | videosAddValidator, | 31 | videosAddValidator, |
36 | videosCustomGetValidator, | 32 | videosCustomGetValidator, |
37 | videosGetValidator, | 33 | videosGetValidator, |
@@ -61,11 +57,15 @@ import { CONFIG } from '../../../initializers/config' | |||
61 | import { sequelizeTypescript } from '../../../initializers/database' | 57 | import { sequelizeTypescript } from '../../../initializers/database' |
62 | import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail' | 58 | import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail' |
63 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 59 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
64 | import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding' | ||
65 | import { Hooks } from '../../../lib/plugins/hooks' | 60 | import { Hooks } from '../../../lib/plugins/hooks' |
66 | import { MVideoDetails, MVideoFullLight } from '@server/typings/models' | 61 | import { MVideoDetails, MVideoFullLight } from '@server/typings/models' |
67 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 62 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
68 | import { getVideoFilePath } from '@server/lib/video-paths' | 63 | import { getVideoFilePath } from '@server/lib/video-paths' |
64 | import toInt from 'validator/lib/toInt' | ||
65 | import { addOptimizeOrMergeAudioJob } from '@server/helpers/video' | ||
66 | import { getServerActor } from '@server/models/application/application' | ||
67 | import { changeVideoChannelShare } from '@server/lib/activitypub/share' | ||
68 | import { getVideoActivityPubUrl } from '@server/lib/activitypub/url' | ||
69 | 69 | ||
70 | const auditLogger = auditLoggerFactory('videos') | 70 | const auditLogger = auditLoggerFactory('videos') |
71 | const videosRouter = express.Router() | 71 | const videosRouter = express.Router() |
@@ -128,6 +128,10 @@ videosRouter.get('/:id/description', | |||
128 | asyncMiddleware(videosGetValidator), | 128 | asyncMiddleware(videosGetValidator), |
129 | asyncMiddleware(getVideoDescription) | 129 | asyncMiddleware(getVideoDescription) |
130 | ) | 130 | ) |
131 | videosRouter.get('/:id/metadata/:videoFileId', | ||
132 | asyncMiddleware(videoFileMetadataGetValidator), | ||
133 | asyncMiddleware(getVideoFileMetadata) | ||
134 | ) | ||
131 | videosRouter.get('/:id', | 135 | videosRouter.get('/:id', |
132 | optionalAuthenticate, | 136 | optionalAuthenticate, |
133 | asyncMiddleware(videosCustomGetValidator('only-video-with-rights')), | 137 | asyncMiddleware(videosCustomGetValidator('only-video-with-rights')), |
@@ -135,7 +139,7 @@ videosRouter.get('/:id', | |||
135 | asyncMiddleware(getVideo) | 139 | asyncMiddleware(getVideo) |
136 | ) | 140 | ) |
137 | videosRouter.post('/:id/views', | 141 | videosRouter.post('/:id/views', |
138 | asyncMiddleware(videosGetValidator), | 142 | asyncMiddleware(videosCustomGetValidator('only-immutable-attributes')), |
139 | asyncMiddleware(viewVideo) | 143 | asyncMiddleware(viewVideo) |
140 | ) | 144 | ) |
141 | 145 | ||
@@ -206,7 +210,8 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
206 | const videoFile = new VideoFileModel({ | 210 | const videoFile = new VideoFileModel({ |
207 | extname: extname(videoPhysicalFile.filename), | 211 | extname: extname(videoPhysicalFile.filename), |
208 | size: videoPhysicalFile.size, | 212 | size: videoPhysicalFile.size, |
209 | videoStreamingPlaylistId: null | 213 | videoStreamingPlaylistId: null, |
214 | metadata: await getMetadataFromFile<any>(videoPhysicalFile.path) | ||
210 | }) | 215 | }) |
211 | 216 | ||
212 | if (videoFile.isAudio()) { | 217 | if (videoFile.isAudio()) { |
@@ -289,25 +294,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
289 | Notifier.Instance.notifyOnNewVideoIfNeeded(videoCreated) | 294 | Notifier.Instance.notifyOnNewVideoIfNeeded(videoCreated) |
290 | 295 | ||
291 | if (video.state === VideoState.TO_TRANSCODE) { | 296 | if (video.state === VideoState.TO_TRANSCODE) { |
292 | // Put uuid because we don't have id auto incremented for now | 297 | await addOptimizeOrMergeAudioJob(videoCreated, videoFile) |
293 | let dataInput: VideoTranscodingPayload | ||
294 | |||
295 | if (videoFile.isAudio()) { | ||
296 | dataInput = { | ||
297 | type: 'merge-audio' as 'merge-audio', | ||
298 | resolution: DEFAULT_AUDIO_RESOLUTION, | ||
299 | videoUUID: videoCreated.uuid, | ||
300 | isNewVideo: true | ||
301 | } | ||
302 | } else { | ||
303 | dataInput = { | ||
304 | type: 'optimize' as 'optimize', | ||
305 | videoUUID: videoCreated.uuid, | ||
306 | isNewVideo: true | ||
307 | } | ||
308 | } | ||
309 | |||
310 | await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) | ||
311 | } | 298 | } |
312 | 299 | ||
313 | Hooks.runAction('action:api.video.uploaded', { video: videoCreated }) | 300 | Hooks.runAction('action:api.video.uploaded', { video: videoCreated }) |
@@ -452,14 +439,13 @@ async function getVideo (req: express.Request, res: express.Response) { | |||
452 | 439 | ||
453 | if (video.isOutdated()) { | 440 | if (video.isOutdated()) { |
454 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } }) | 441 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } }) |
455 | .catch(err => logger.error('Cannot create AP refresher job for video %s.', video.url, { err })) | ||
456 | } | 442 | } |
457 | 443 | ||
458 | return res.json(video.toFormattedDetailsJSON()) | 444 | return res.json(video.toFormattedDetailsJSON()) |
459 | } | 445 | } |
460 | 446 | ||
461 | async function viewVideo (req: express.Request, res: express.Response) { | 447 | async function viewVideo (req: express.Request, res: express.Response) { |
462 | const videoInstance = res.locals.videoAll | 448 | const videoInstance = res.locals.onlyImmutableVideo |
463 | 449 | ||
464 | const ip = req.ip | 450 | const ip = req.ip |
465 | const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) | 451 | const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) |
@@ -494,6 +480,11 @@ async function getVideoDescription (req: express.Request, res: express.Response) | |||
494 | return res.json({ description }) | 480 | return res.json({ description }) |
495 | } | 481 | } |
496 | 482 | ||
483 | async function getVideoFileMetadata (req: express.Request, res: express.Response) { | ||
484 | const videoFile = await VideoFileModel.loadWithMetadata(toInt(req.params.videoFileId)) | ||
485 | return res.json(videoFile.metadata) | ||
486 | } | ||
487 | |||
497 | async function listVideos (req: express.Request, res: express.Response) { | 488 | async function listVideos (req: express.Request, res: express.Response) { |
498 | const countVideos = getCountVideos(req) | 489 | const countVideos = getCountVideos(req) |
499 | 490 | ||
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts index 41d7cdc43..540a49010 100644 --- a/server/controllers/api/videos/ownership.ts +++ b/server/controllers/api/videos/ownership.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { sequelizeTypescript } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers/database' |
4 | import { | 4 | import { |
5 | asyncMiddleware, | 5 | asyncMiddleware, |
6 | asyncRetryTransactionMiddleware, | 6 | asyncRetryTransactionMiddleware, |
@@ -15,7 +15,7 @@ import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ow | |||
15 | import { VideoChangeOwnershipStatus, VideoState } from '../../../../shared/models/videos' | 15 | import { VideoChangeOwnershipStatus, VideoState } from '../../../../shared/models/videos' |
16 | import { VideoChannelModel } from '../../../models/video/video-channel' | 16 | import { VideoChannelModel } from '../../../models/video/video-channel' |
17 | import { getFormattedObjects } from '../../../helpers/utils' | 17 | import { getFormattedObjects } from '../../../helpers/utils' |
18 | import { changeVideoChannelShare } from '../../../lib/activitypub' | 18 | import { changeVideoChannelShare } from '../../../lib/activitypub/share' |
19 | import { sendUpdateVideo } from '../../../lib/activitypub/send' | 19 | import { sendUpdateVideo } from '../../../lib/activitypub/send' |
20 | import { VideoModel } from '../../../models/video/video' | 20 | import { VideoModel } from '../../../models/video/video' |
21 | import { MVideoFullLight } from '@server/typings/models' | 21 | import { MVideoFullLight } from '@server/typings/models' |
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts index 3d2f3d728..3ee365289 100644 --- a/server/controllers/api/videos/rate.ts +++ b/server/controllers/api/videos/rate.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import { UserVideoRateUpdate } from '../../../../shared' | 2 | import { UserVideoRateUpdate } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { VIDEO_RATE_TYPES } from '../../../initializers/constants' | 4 | import { VIDEO_RATE_TYPES } from '../../../initializers/constants' |
5 | import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub' | 5 | import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub/video-rates' |
6 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares' | 6 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares' |
7 | import { AccountModel } from '../../../models/account/account' | 7 | import { AccountModel } from '../../../models/account/account' |
8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
diff --git a/server/controllers/bots.ts b/server/controllers/bots.ts index 63280dabb..8d1fa72f3 100644 --- a/server/controllers/bots.ts +++ b/server/controllers/bots.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { asyncMiddleware } from '../middlewares' | 2 | import { asyncMiddleware } from '../middlewares' |
3 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' | 3 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' |
4 | import * as sitemapModule from 'sitemap' | 4 | import { SitemapStream, streamToPromise } from 'sitemap' |
5 | import { VideoModel } from '../models/video/video' | 5 | import { VideoModel } from '../models/video/video' |
6 | import { VideoChannelModel } from '../models/video/video-channel' | 6 | import { VideoChannelModel } from '../models/video/video-channel' |
7 | import { AccountModel } from '../models/account/account' | 7 | import { AccountModel } from '../models/account/account' |
@@ -33,12 +33,14 @@ async function getSitemap (req: express.Request, res: express.Response) { | |||
33 | urls = urls.concat(await getSitemapVideoChannelUrls()) | 33 | urls = urls.concat(await getSitemapVideoChannelUrls()) |
34 | urls = urls.concat(await getSitemapAccountUrls()) | 34 | urls = urls.concat(await getSitemapAccountUrls()) |
35 | 35 | ||
36 | const sitemap = sitemapModule.createSitemap({ | 36 | const sitemapStream = new SitemapStream({ hostname: WEBSERVER.URL }) |
37 | hostname: WEBSERVER.URL, | 37 | |
38 | urls: urls | 38 | for (const urlObj of urls) { |
39 | }) | 39 | sitemapStream.write(urlObj) |
40 | } | ||
41 | sitemapStream.end() | ||
40 | 42 | ||
41 | const xml = sitemap.toXML() | 43 | const xml = await streamToPromise(sitemapStream) |
42 | 44 | ||
43 | res.header('Content-Type', 'application/xml') | 45 | res.header('Content-Type', 'application/xml') |
44 | res.send(xml) | 46 | res.send(xml) |
diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 56685f102..08c0f1fa6 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts | |||
@@ -72,11 +72,11 @@ export { | |||
72 | 72 | ||
73 | // --------------------------------------------------------------------------- | 73 | // --------------------------------------------------------------------------- |
74 | 74 | ||
75 | async function serveServerTranslations (req: express.Request, res: express.Response) { | 75 | function serveServerTranslations (req: express.Request, res: express.Response) { |
76 | const locale = req.params.locale | 76 | const locale = req.params.locale |
77 | const file = req.params.file | 77 | const file = req.params.file |
78 | 78 | ||
79 | if (is18nLocale(locale) && LOCALE_FILES.indexOf(file) !== -1) { | 79 | if (is18nLocale(locale) && LOCALE_FILES.includes(file)) { |
80 | const completeLocale = getCompleteLocale(locale) | 80 | const completeLocale = getCompleteLocale(locale) |
81 | const completeFileLocale = buildFileLocale(completeLocale) | 81 | const completeFileLocale = buildFileLocale(completeLocale) |
82 | 82 | ||
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index 72628dffb..cb82bfc6d 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -67,7 +67,7 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res | |||
67 | const feed = initFeed(name, description) | 67 | const feed = initFeed(name, description) |
68 | 68 | ||
69 | // Adding video items to the feed, one at a time | 69 | // Adding video items to the feed, one at a time |
70 | comments.forEach(comment => { | 70 | for (const comment of comments) { |
71 | const link = WEBSERVER.URL + comment.getCommentStaticPath() | 71 | const link = WEBSERVER.URL + comment.getCommentStaticPath() |
72 | 72 | ||
73 | let title = comment.Video.name | 73 | let title = comment.Video.name |
@@ -89,7 +89,7 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res | |||
89 | author, | 89 | author, |
90 | date: comment.createdAt | 90 | date: comment.createdAt |
91 | }) | 91 | }) |
92 | }) | 92 | } |
93 | 93 | ||
94 | // Now the feed generation is done, let's send it! | 94 | // Now the feed generation is done, let's send it! |
95 | return sendFeed(feed, req, res) | 95 | return sendFeed(feed, req, res) |
diff --git a/server/controllers/plugins.ts b/server/controllers/plugins.ts index 1caee9a29..f88a1632d 100644 --- a/server/controllers/plugins.ts +++ b/server/controllers/plugins.ts | |||
@@ -2,11 +2,12 @@ import * as express from 'express' | |||
2 | import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants' | 2 | import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager' | 4 | import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager' |
5 | import { servePluginStaticDirectoryValidator } from '../middlewares/validators/plugins' | 5 | import { getPluginValidator, pluginStaticDirectoryValidator, getExternalAuthValidator } from '../middlewares/validators/plugins' |
6 | import { serveThemeCSSValidator } from '../middlewares/validators/themes' | 6 | import { serveThemeCSSValidator } from '../middlewares/validators/themes' |
7 | import { PluginType } from '../../shared/models/plugins/plugin.type' | 7 | import { PluginType } from '../../shared/models/plugins/plugin.type' |
8 | import { isTestInstance } from '../helpers/core-utils' | 8 | import { isTestInstance } from '../helpers/core-utils' |
9 | import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n' | 9 | import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n' |
10 | import { logger } from '@server/helpers/logger' | ||
10 | 11 | ||
11 | const sendFileOptions = { | 12 | const sendFileOptions = { |
12 | maxAge: '30 days', | 13 | maxAge: '30 days', |
@@ -23,23 +24,43 @@ pluginsRouter.get('/plugins/translations/:locale.json', | |||
23 | getPluginTranslations | 24 | getPluginTranslations |
24 | ) | 25 | ) |
25 | 26 | ||
27 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName', | ||
28 | getPluginValidator(PluginType.PLUGIN), | ||
29 | getExternalAuthValidator, | ||
30 | handleAuthInPlugin | ||
31 | ) | ||
32 | |||
26 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)', | 33 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)', |
27 | servePluginStaticDirectoryValidator(PluginType.PLUGIN), | 34 | getPluginValidator(PluginType.PLUGIN), |
35 | pluginStaticDirectoryValidator, | ||
28 | servePluginStaticDirectory | 36 | servePluginStaticDirectory |
29 | ) | 37 | ) |
30 | 38 | ||
31 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', | 39 | pluginsRouter.get('/plugins/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', |
32 | servePluginStaticDirectoryValidator(PluginType.PLUGIN), | 40 | getPluginValidator(PluginType.PLUGIN), |
41 | pluginStaticDirectoryValidator, | ||
33 | servePluginClientScripts | 42 | servePluginClientScripts |
34 | ) | 43 | ) |
35 | 44 | ||
45 | pluginsRouter.use('/plugins/:pluginName/router', | ||
46 | getPluginValidator(PluginType.PLUGIN, false), | ||
47 | servePluginCustomRoutes | ||
48 | ) | ||
49 | |||
50 | pluginsRouter.use('/plugins/:pluginName/:pluginVersion/router', | ||
51 | getPluginValidator(PluginType.PLUGIN), | ||
52 | servePluginCustomRoutes | ||
53 | ) | ||
54 | |||
36 | pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)', | 55 | pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)', |
37 | servePluginStaticDirectoryValidator(PluginType.THEME), | 56 | getPluginValidator(PluginType.THEME), |
57 | pluginStaticDirectoryValidator, | ||
38 | servePluginStaticDirectory | 58 | servePluginStaticDirectory |
39 | ) | 59 | ) |
40 | 60 | ||
41 | pluginsRouter.get('/themes/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', | 61 | pluginsRouter.get('/themes/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', |
42 | servePluginStaticDirectoryValidator(PluginType.THEME), | 62 | getPluginValidator(PluginType.THEME), |
63 | pluginStaticDirectoryValidator, | ||
43 | servePluginClientScripts | 64 | servePluginClientScripts |
44 | ) | 65 | ) |
45 | 66 | ||
@@ -85,22 +106,27 @@ function servePluginStaticDirectory (req: express.Request, res: express.Response | |||
85 | const [ directory, ...file ] = staticEndpoint.split('/') | 106 | const [ directory, ...file ] = staticEndpoint.split('/') |
86 | 107 | ||
87 | const staticPath = plugin.staticDirs[directory] | 108 | const staticPath = plugin.staticDirs[directory] |
88 | if (!staticPath) { | 109 | if (!staticPath) return res.sendStatus(404) |
89 | return res.sendStatus(404) | ||
90 | } | ||
91 | 110 | ||
92 | const filepath = file.join('/') | 111 | const filepath = file.join('/') |
93 | return res.sendFile(join(plugin.path, staticPath, filepath), sendFileOptions) | 112 | return res.sendFile(join(plugin.path, staticPath, filepath), sendFileOptions) |
94 | } | 113 | } |
95 | 114 | ||
115 | function servePluginCustomRoutes (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
116 | const plugin: RegisteredPlugin = res.locals.registeredPlugin | ||
117 | const router = PluginManager.Instance.getRouter(plugin.npmName) | ||
118 | |||
119 | if (!router) return res.sendStatus(404) | ||
120 | |||
121 | return router(req, res, next) | ||
122 | } | ||
123 | |||
96 | function servePluginClientScripts (req: express.Request, res: express.Response) { | 124 | function servePluginClientScripts (req: express.Request, res: express.Response) { |
97 | const plugin: RegisteredPlugin = res.locals.registeredPlugin | 125 | const plugin: RegisteredPlugin = res.locals.registeredPlugin |
98 | const staticEndpoint = req.params.staticEndpoint | 126 | const staticEndpoint = req.params.staticEndpoint |
99 | 127 | ||
100 | const file = plugin.clientScripts[staticEndpoint] | 128 | const file = plugin.clientScripts[staticEndpoint] |
101 | if (!file) { | 129 | if (!file) return res.sendStatus(404) |
102 | return res.sendStatus(404) | ||
103 | } | ||
104 | 130 | ||
105 | return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions) | 131 | return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions) |
106 | } | 132 | } |
@@ -115,3 +141,14 @@ function serveThemeCSSDirectory (req: express.Request, res: express.Response) { | |||
115 | 141 | ||
116 | return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions) | 142 | return res.sendFile(join(plugin.path, staticEndpoint), sendFileOptions) |
117 | } | 143 | } |
144 | |||
145 | function handleAuthInPlugin (req: express.Request, res: express.Response) { | ||
146 | const authOptions = res.locals.externalAuth | ||
147 | |||
148 | try { | ||
149 | logger.debug('Forwarding auth plugin request in %s of plugin %s.', authOptions.authName, res.locals.registeredPlugin.npmName) | ||
150 | authOptions.onAuthRequest(req, res) | ||
151 | } catch (err) { | ||
152 | logger.error('Forward request error in auth %s of plugin %s.', authOptions.authName, res.locals.registeredPlugin.npmName, { err }) | ||
153 | } | ||
154 | } | ||
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index a4bb3a4d9..271b788f6 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -1,15 +1,15 @@ | |||
1 | import * as cors from 'cors' | 1 | import * as cors from 'cors' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { | 3 | import { |
4 | CONSTRAINTS_FIELDS, | ||
5 | DEFAULT_THEME_NAME, | ||
4 | HLS_STREAMING_PLAYLIST_DIRECTORY, | 6 | HLS_STREAMING_PLAYLIST_DIRECTORY, |
5 | PEERTUBE_VERSION, | 7 | PEERTUBE_VERSION, |
6 | ROUTE_CACHE_LIFETIME, | 8 | ROUTE_CACHE_LIFETIME, |
7 | STATIC_DOWNLOAD_PATHS, | 9 | STATIC_DOWNLOAD_PATHS, |
8 | STATIC_MAX_AGE, | 10 | STATIC_MAX_AGE, |
9 | STATIC_PATHS, | 11 | STATIC_PATHS, |
10 | WEBSERVER, | 12 | WEBSERVER |
11 | CONSTRAINTS_FIELDS, | ||
12 | DEFAULT_THEME_NAME | ||
13 | } from '../initializers/constants' | 13 | } from '../initializers/constants' |
14 | import { cacheRoute } from '../middlewares/cache' | 14 | import { cacheRoute } from '../middlewares/cache' |
15 | import { asyncMiddleware, videosDownloadValidator } from '../middlewares' | 15 | import { asyncMiddleware, videosDownloadValidator } from '../middlewares' |
@@ -19,8 +19,7 @@ import { VideoCommentModel } from '../models/video/video-comment' | |||
19 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' | 19 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' |
20 | import { join } from 'path' | 20 | import { join } from 'path' |
21 | import { root } from '../helpers/core-utils' | 21 | import { root } from '../helpers/core-utils' |
22 | import { CONFIG } from '../initializers/config' | 22 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
23 | import { Emailer } from '../lib/emailer' | ||
24 | import { getPreview, getVideoCaption } from './lazy-static' | 23 | import { getPreview, getVideoCaption } from './lazy-static' |
25 | import { VideoStreamingPlaylistType } from '@shared/models/videos/video-streaming-playlist.type' | 24 | import { VideoStreamingPlaylistType } from '@shared/models/videos/video-streaming-playlist.type' |
26 | import { MVideoFile, MVideoFullLight } from '@server/typings/models' | 25 | import { MVideoFile, MVideoFullLight } from '@server/typings/models' |
@@ -45,12 +44,12 @@ staticRouter.use( | |||
45 | staticRouter.use( | 44 | staticRouter.use( |
46 | STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent', | 45 | STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+).torrent', |
47 | asyncMiddleware(videosDownloadValidator), | 46 | asyncMiddleware(videosDownloadValidator), |
48 | asyncMiddleware(downloadTorrent) | 47 | downloadTorrent |
49 | ) | 48 | ) |
50 | staticRouter.use( | 49 | staticRouter.use( |
51 | STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+)-hls.torrent', | 50 | STATIC_DOWNLOAD_PATHS.TORRENTS + ':id-:resolution([0-9]+)-hls.torrent', |
52 | asyncMiddleware(videosDownloadValidator), | 51 | asyncMiddleware(videosDownloadValidator), |
53 | asyncMiddleware(downloadHLSVideoFileTorrent) | 52 | downloadHLSVideoFileTorrent |
54 | ) | 53 | ) |
55 | 54 | ||
56 | // Videos path for webseeding | 55 | // Videos path for webseeding |
@@ -68,13 +67,13 @@ staticRouter.use( | |||
68 | staticRouter.use( | 67 | staticRouter.use( |
69 | STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension', | 68 | STATIC_DOWNLOAD_PATHS.VIDEOS + ':id-:resolution([0-9]+).:extension', |
70 | asyncMiddleware(videosDownloadValidator), | 69 | asyncMiddleware(videosDownloadValidator), |
71 | asyncMiddleware(downloadVideoFile) | 70 | downloadVideoFile |
72 | ) | 71 | ) |
73 | 72 | ||
74 | staticRouter.use( | 73 | staticRouter.use( |
75 | STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension', | 74 | STATIC_DOWNLOAD_PATHS.HLS_VIDEOS + ':id-:resolution([0-9]+)-fragmented.:extension', |
76 | asyncMiddleware(videosDownloadValidator), | 75 | asyncMiddleware(videosDownloadValidator), |
77 | asyncMiddleware(downloadHLSVideoFile) | 76 | downloadHLSVideoFile |
78 | ) | 77 | ) |
79 | 78 | ||
80 | // HLS | 79 | // HLS |
@@ -235,6 +234,12 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { | |||
235 | nodeName: CONFIG.INSTANCE.NAME, | 234 | nodeName: CONFIG.INSTANCE.NAME, |
236 | nodeDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, | 235 | nodeDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, |
237 | nodeConfig: { | 236 | nodeConfig: { |
237 | search: { | ||
238 | remoteUri: { | ||
239 | users: CONFIG.SEARCH.REMOTE_URI.USERS, | ||
240 | anonymous: CONFIG.SEARCH.REMOTE_URI.ANONYMOUS | ||
241 | } | ||
242 | }, | ||
238 | plugin: { | 243 | plugin: { |
239 | registered: getRegisteredPlugins() | 244 | registered: getRegisteredPlugins() |
240 | }, | 245 | }, |
@@ -243,7 +248,7 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { | |||
243 | default: getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME) | 248 | default: getThemeOrDefault(CONFIG.THEME.DEFAULT, DEFAULT_THEME_NAME) |
244 | }, | 249 | }, |
245 | email: { | 250 | email: { |
246 | enabled: Emailer.isEnabled() | 251 | enabled: isEmailEnabled() |
247 | }, | 252 | }, |
248 | contactForm: { | 253 | contactForm: { |
249 | enabled: CONFIG.CONTACT_FORM.ENABLED | 254 | enabled: CONFIG.CONTACT_FORM.ENABLED |
@@ -325,7 +330,7 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { | |||
325 | return res.send(json).end() | 330 | return res.send(json).end() |
326 | } | 331 | } |
327 | 332 | ||
328 | async function downloadTorrent (req: express.Request, res: express.Response) { | 333 | function downloadTorrent (req: express.Request, res: express.Response) { |
329 | const video = res.locals.videoAll | 334 | const video = res.locals.videoAll |
330 | 335 | ||
331 | const videoFile = getVideoFile(req, video.VideoFiles) | 336 | const videoFile = getVideoFile(req, video.VideoFiles) |
@@ -334,7 +339,7 @@ async function downloadTorrent (req: express.Request, res: express.Response) { | |||
334 | return res.download(getTorrentFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p.torrent`) | 339 | return res.download(getTorrentFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p.torrent`) |
335 | } | 340 | } |
336 | 341 | ||
337 | async function downloadHLSVideoFileTorrent (req: express.Request, res: express.Response) { | 342 | function downloadHLSVideoFileTorrent (req: express.Request, res: express.Response) { |
338 | const video = res.locals.videoAll | 343 | const video = res.locals.videoAll |
339 | 344 | ||
340 | const playlist = getHLSPlaylist(video) | 345 | const playlist = getHLSPlaylist(video) |
@@ -346,7 +351,7 @@ async function downloadHLSVideoFileTorrent (req: express.Request, res: express.R | |||
346 | return res.download(getTorrentFilePath(playlist, videoFile), `${video.name}-${videoFile.resolution}p-hls.torrent`) | 351 | return res.download(getTorrentFilePath(playlist, videoFile), `${video.name}-${videoFile.resolution}p-hls.torrent`) |
347 | } | 352 | } |
348 | 353 | ||
349 | async function downloadVideoFile (req: express.Request, res: express.Response) { | 354 | function downloadVideoFile (req: express.Request, res: express.Response) { |
350 | const video = res.locals.videoAll | 355 | const video = res.locals.videoAll |
351 | 356 | ||
352 | const videoFile = getVideoFile(req, video.VideoFiles) | 357 | const videoFile = getVideoFile(req, video.VideoFiles) |
@@ -355,7 +360,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response) { | |||
355 | return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`) | 360 | return res.download(getVideoFilePath(video, videoFile), `${video.name}-${videoFile.resolution}p${videoFile.extname}`) |
356 | } | 361 | } |
357 | 362 | ||
358 | async function downloadHLSVideoFile (req: express.Request, res: express.Response) { | 363 | function downloadHLSVideoFile (req: express.Request, res: express.Response) { |
359 | const video = res.locals.videoAll | 364 | const video = res.locals.videoAll |
360 | const playlist = getHLSPlaylist(video) | 365 | const playlist = getHLSPlaylist(video) |
361 | if (!playlist) return res.status(404).end | 366 | if (!playlist) return res.status(404).end |
diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts index 2ae1cf86c..4f756fc0a 100644 --- a/server/controllers/tracker.ts +++ b/server/controllers/tracker.ts | |||
@@ -6,7 +6,6 @@ import * as proxyAddr from 'proxy-addr' | |||
6 | import { Server as WebSocketServer } from 'ws' | 6 | import { Server as WebSocketServer } from 'ws' |
7 | import { TRACKER_RATE_LIMITS } from '../initializers/constants' | 7 | import { TRACKER_RATE_LIMITS } from '../initializers/constants' |
8 | import { VideoFileModel } from '../models/video/video-file' | 8 | import { VideoFileModel } from '../models/video/video-file' |
9 | import { parse } from 'url' | ||
10 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 9 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
11 | import { CONFIG } from '../initializers/config' | 10 | import { CONFIG } from '../initializers/config' |
12 | 11 | ||
@@ -38,11 +37,11 @@ const trackerServer = new TrackerServer({ | |||
38 | 37 | ||
39 | const key = ip + '-' + infoHash | 38 | const key = ip + '-' + infoHash |
40 | 39 | ||
41 | peersIps[ ip ] = peersIps[ ip ] ? peersIps[ ip ] + 1 : 1 | 40 | peersIps[ip] = peersIps[ip] ? peersIps[ip] + 1 : 1 |
42 | peersIpInfoHash[ key ] = peersIpInfoHash[ key ] ? peersIpInfoHash[ key ] + 1 : 1 | 41 | peersIpInfoHash[key] = peersIpInfoHash[key] ? peersIpInfoHash[key] + 1 : 1 |
43 | 42 | ||
44 | if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[ key ] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) { | 43 | if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[key] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) { |
45 | return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`)) | 44 | return cb(new Error(`Too many requests (${peersIpInfoHash[key]} of ip ${ip} for torrent ${infoHash}`)) |
46 | } | 45 | } |
47 | 46 | ||
48 | try { | 47 | try { |
@@ -87,10 +86,8 @@ function createWebsocketTrackerServer (app: express.Application) { | |||
87 | trackerServer.onWebSocketConnection(ws) | 86 | trackerServer.onWebSocketConnection(ws) |
88 | }) | 87 | }) |
89 | 88 | ||
90 | server.on('upgrade', (request, socket, head) => { | 89 | server.on('upgrade', (request: express.Request, socket, head) => { |
91 | const pathname = parse(request.url).pathname | 90 | if (request.url === '/tracker/socket') { |
92 | |||
93 | if (pathname === '/tracker/socket') { | ||
94 | wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request)) | 91 | wss.handleUpgrade(request, socket, head, ws => wss.emit('connection', ws, request)) |
95 | } | 92 | } |
96 | 93 | ||
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts index fc9575160..5c308d9ad 100644 --- a/server/controllers/webfinger.ts +++ b/server/controllers/webfinger.ts | |||
@@ -1,9 +1,12 @@ | |||
1 | import * as cors from 'cors' | ||
1 | import * as express from 'express' | 2 | import * as express from 'express' |
2 | import { asyncMiddleware } from '../middlewares' | 3 | import { asyncMiddleware } from '../middlewares' |
3 | import { webfingerValidator } from '../middlewares/validators' | 4 | import { webfingerValidator } from '../middlewares/validators' |
4 | 5 | ||
5 | const webfingerRouter = express.Router() | 6 | const webfingerRouter = express.Router() |
6 | 7 | ||
8 | webfingerRouter.use(cors()) | ||
9 | |||
7 | webfingerRouter.get('/.well-known/webfinger', | 10 | webfingerRouter.get('/.well-known/webfinger', |
8 | asyncMiddleware(webfingerValidator), | 11 | asyncMiddleware(webfingerValidator), |
9 | webfingerController | 12 | webfingerController |
@@ -18,7 +21,7 @@ export { | |||
18 | // --------------------------------------------------------------------------- | 21 | // --------------------------------------------------------------------------- |
19 | 22 | ||
20 | function webfingerController (req: express.Request, res: express.Response) { | 23 | function webfingerController (req: express.Request, res: express.Response) { |
21 | const actor = res.locals.actorFull | 24 | const actor = res.locals.actorUrl |
22 | 25 | ||
23 | const json = { | 26 | const json = { |
24 | subject: req.query.resource, | 27 | subject: req.query.resource, |
@@ -32,5 +35,5 @@ function webfingerController (req: express.Request, res: express.Response) { | |||
32 | ] | 35 | ] |
33 | } | 36 | } |
34 | 37 | ||
35 | return res.json(json).end() | 38 | return res.json(json) |
36 | } | 39 | } |
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 239d8291d..aeb8fde01 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -2,21 +2,35 @@ import * as Bluebird from 'bluebird' | |||
2 | import validator from 'validator' | 2 | import validator from 'validator' |
3 | import { ResultList } from '../../shared/models' | 3 | import { ResultList } from '../../shared/models' |
4 | import { Activity } from '../../shared/models/activitypub' | 4 | import { Activity } from '../../shared/models/activitypub' |
5 | import { ACTIVITY_PUB } from '../initializers/constants' | 5 | import { ACTIVITY_PUB, REMOTE_SCHEME } from '../initializers/constants' |
6 | import { signJsonLDObject } from './peertube-crypto' | 6 | import { signJsonLDObject } from './peertube-crypto' |
7 | import { pageToStartAndCount } from './core-utils' | 7 | import { pageToStartAndCount } from './core-utils' |
8 | import { parse } from 'url' | 8 | import { URL } from 'url' |
9 | import { MActor } from '../typings/models' | 9 | import { MActor, MVideoAccountLight } from '../typings/models' |
10 | 10 | import { ContextType } from '@shared/models/activitypub/context' | |
11 | function activityPubContextify <T> (data: T) { | 11 | |
12 | return Object.assign(data, { | 12 | function getContextData (type: ContextType) { |
13 | '@context': [ | 13 | const context: any[] = [ |
14 | 'https://www.w3.org/ns/activitystreams', | 14 | 'https://www.w3.org/ns/activitystreams', |
15 | 'https://w3id.org/security/v1', | 15 | 'https://w3id.org/security/v1', |
16 | { | 16 | { |
17 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', | 17 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' |
18 | pt: 'https://joinpeertube.org/ns#', | 18 | } |
19 | sc: 'http://schema.org#', | 19 | ] |
20 | |||
21 | if (type !== 'View' && type !== 'Announce') { | ||
22 | const additional = { | ||
23 | pt: 'https://joinpeertube.org/ns#', | ||
24 | sc: 'http://schema.org#' | ||
25 | } | ||
26 | |||
27 | if (type === 'CacheFile') { | ||
28 | Object.assign(additional, { | ||
29 | expires: 'sc:expires', | ||
30 | CacheFile: 'pt:CacheFile' | ||
31 | }) | ||
32 | } else { | ||
33 | Object.assign(additional, { | ||
20 | Hashtag: 'as:Hashtag', | 34 | Hashtag: 'as:Hashtag', |
21 | uuid: 'sc:identifier', | 35 | uuid: 'sc:identifier', |
22 | category: 'sc:category', | 36 | category: 'sc:category', |
@@ -24,8 +38,7 @@ function activityPubContextify <T> (data: T) { | |||
24 | subtitleLanguage: 'sc:subtitleLanguage', | 38 | subtitleLanguage: 'sc:subtitleLanguage', |
25 | sensitive: 'as:sensitive', | 39 | sensitive: 'as:sensitive', |
26 | language: 'sc:inLanguage', | 40 | language: 'sc:inLanguage', |
27 | expires: 'sc:expires', | 41 | |
28 | CacheFile: 'pt:CacheFile', | ||
29 | Infohash: 'pt:Infohash', | 42 | Infohash: 'pt:Infohash', |
30 | originallyPublishedAt: 'sc:datePublished', | 43 | originallyPublishedAt: 'sc:datePublished', |
31 | views: { | 44 | views: { |
@@ -71,9 +84,7 @@ function activityPubContextify <T> (data: T) { | |||
71 | support: { | 84 | support: { |
72 | '@type': 'sc:Text', | 85 | '@type': 'sc:Text', |
73 | '@id': 'pt:support' | 86 | '@id': 'pt:support' |
74 | } | 87 | }, |
75 | }, | ||
76 | { | ||
77 | likes: { | 88 | likes: { |
78 | '@id': 'as:likes', | 89 | '@id': 'as:likes', |
79 | '@type': '@id' | 90 | '@type': '@id' |
@@ -94,9 +105,19 @@ function activityPubContextify <T> (data: T) { | |||
94 | '@id': 'as:comments', | 105 | '@id': 'as:comments', |
95 | '@type': '@id' | 106 | '@type': '@id' |
96 | } | 107 | } |
97 | } | 108 | }) |
98 | ] | 109 | } |
99 | }) | 110 | |
111 | context.push(additional) | ||
112 | } | ||
113 | |||
114 | return { | ||
115 | '@context': context | ||
116 | } | ||
117 | } | ||
118 | |||
119 | function activityPubContextify <T> (data: T, type: ContextType = 'All') { | ||
120 | return Object.assign({}, data, getContextData(type)) | ||
100 | } | 121 | } |
101 | 122 | ||
102 | type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>> | 123 | type ActivityPubCollectionPaginationHandler = (start: number, count: number) => Bluebird<ResultList<any>> | Promise<ResultList<any>> |
@@ -148,8 +169,8 @@ async function activityPubCollectionPagination ( | |||
148 | 169 | ||
149 | } | 170 | } |
150 | 171 | ||
151 | function buildSignedActivity (byActor: MActor, data: Object) { | 172 | function buildSignedActivity (byActor: MActor, data: Object, contextType?: ContextType) { |
152 | const activity = activityPubContextify(data) | 173 | const activity = activityPubContextify(data, contextType) |
153 | 174 | ||
154 | return signJsonLDObject(byActor, activity) as Promise<Activity> | 175 | return signJsonLDObject(byActor, activity) as Promise<Activity> |
155 | } | 176 | } |
@@ -161,12 +182,18 @@ function getAPId (activity: string | { id: string }) { | |||
161 | } | 182 | } |
162 | 183 | ||
163 | function checkUrlsSameHost (url1: string, url2: string) { | 184 | function checkUrlsSameHost (url1: string, url2: string) { |
164 | const idHost = parse(url1).host | 185 | const idHost = new URL(url1).host |
165 | const actorHost = parse(url2).host | 186 | const actorHost = new URL(url2).host |
166 | 187 | ||
167 | return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase() | 188 | return idHost && actorHost && idHost.toLowerCase() === actorHost.toLowerCase() |
168 | } | 189 | } |
169 | 190 | ||
191 | function buildRemoteVideoBaseUrl (video: MVideoAccountLight, path: string) { | ||
192 | const host = video.VideoChannel.Account.Actor.Server.host | ||
193 | |||
194 | return REMOTE_SCHEME.HTTP + '://' + host + path | ||
195 | } | ||
196 | |||
170 | // --------------------------------------------------------------------------- | 197 | // --------------------------------------------------------------------------- |
171 | 198 | ||
172 | export { | 199 | export { |
@@ -174,5 +201,6 @@ export { | |||
174 | getAPId, | 201 | getAPId, |
175 | activityPubContextify, | 202 | activityPubContextify, |
176 | activityPubCollectionPagination, | 203 | activityPubCollectionPagination, |
177 | buildSignedActivity | 204 | buildSignedActivity, |
205 | buildRemoteVideoBaseUrl | ||
178 | } | 206 | } |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 9b258dc3a..0bbfbc753 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -36,7 +36,7 @@ const auditLogger = winston.createLogger({ | |||
36 | maxFiles: 5, | 36 | maxFiles: 5, |
37 | format: winston.format.combine( | 37 | format: winston.format.combine( |
38 | winston.format.timestamp(), | 38 | winston.format.timestamp(), |
39 | labelFormatter, | 39 | labelFormatter(), |
40 | winston.format.splat(), | 40 | winston.format.splat(), |
41 | jsonLoggerFormat | 41 | jsonLoggerFormat |
42 | ) | 42 | ) |
@@ -81,7 +81,8 @@ function auditLoggerFactory (domain: string) { | |||
81 | } | 81 | } |
82 | 82 | ||
83 | abstract class EntityAuditView { | 83 | abstract class EntityAuditView { |
84 | constructor (private keysToKeep: Array<string>, private prefix: string, private entityInfos: object) { } | 84 | constructor (private readonly keysToKeep: string[], private readonly prefix: string, private readonly entityInfos: object) { } |
85 | |||
85 | toLogKeys (): object { | 86 | toLogKeys (): object { |
86 | return chain(flatten(this.entityInfos, { delimiter: '-', safe: true })) | 87 | return chain(flatten(this.entityInfos, { delimiter: '-', safe: true })) |
87 | .pick(this.keysToKeep) | 88 | .pick(this.keysToKeep) |
@@ -121,7 +122,7 @@ const videoKeysToKeep = [ | |||
121 | 'downloadEnabled' | 122 | 'downloadEnabled' |
122 | ] | 123 | ] |
123 | class VideoAuditView extends EntityAuditView { | 124 | class VideoAuditView extends EntityAuditView { |
124 | constructor (private video: VideoDetails) { | 125 | constructor (private readonly video: VideoDetails) { |
125 | super(videoKeysToKeep, 'video', video) | 126 | super(videoKeysToKeep, 'video', video) |
126 | } | 127 | } |
127 | } | 128 | } |
@@ -132,7 +133,7 @@ const videoImportKeysToKeep = [ | |||
132 | 'video-name' | 133 | 'video-name' |
133 | ] | 134 | ] |
134 | class VideoImportAuditView extends EntityAuditView { | 135 | class VideoImportAuditView extends EntityAuditView { |
135 | constructor (private videoImport: VideoImport) { | 136 | constructor (private readonly videoImport: VideoImport) { |
136 | super(videoImportKeysToKeep, 'video-import', videoImport) | 137 | super(videoImportKeysToKeep, 'video-import', videoImport) |
137 | } | 138 | } |
138 | } | 139 | } |
@@ -151,7 +152,7 @@ const commentKeysToKeep = [ | |||
151 | 'account-name' | 152 | 'account-name' |
152 | ] | 153 | ] |
153 | class CommentAuditView extends EntityAuditView { | 154 | class CommentAuditView extends EntityAuditView { |
154 | constructor (private comment: VideoComment) { | 155 | constructor (private readonly comment: VideoComment) { |
155 | super(commentKeysToKeep, 'comment', comment) | 156 | super(commentKeysToKeep, 'comment', comment) |
156 | } | 157 | } |
157 | } | 158 | } |
@@ -180,7 +181,7 @@ const userKeysToKeep = [ | |||
180 | 'videoChannels' | 181 | 'videoChannels' |
181 | ] | 182 | ] |
182 | class UserAuditView extends EntityAuditView { | 183 | class UserAuditView extends EntityAuditView { |
183 | constructor (private user: User) { | 184 | constructor (private readonly user: User) { |
184 | super(userKeysToKeep, 'user', user) | 185 | super(userKeysToKeep, 'user', user) |
185 | } | 186 | } |
186 | } | 187 | } |
@@ -206,7 +207,7 @@ const channelKeysToKeep = [ | |||
206 | 'ownerAccount-displayedName' | 207 | 'ownerAccount-displayedName' |
207 | ] | 208 | ] |
208 | class VideoChannelAuditView extends EntityAuditView { | 209 | class VideoChannelAuditView extends EntityAuditView { |
209 | constructor (private channel: VideoChannel) { | 210 | constructor (private readonly channel: VideoChannel) { |
210 | super(channelKeysToKeep, 'channel', channel) | 211 | super(channelKeysToKeep, 'channel', channel) |
211 | } | 212 | } |
212 | } | 213 | } |
@@ -221,7 +222,7 @@ const videoAbuseKeysToKeep = [ | |||
221 | 'createdAt' | 222 | 'createdAt' |
222 | ] | 223 | ] |
223 | class VideoAbuseAuditView extends EntityAuditView { | 224 | class VideoAbuseAuditView extends EntityAuditView { |
224 | constructor (private videoAbuse: VideoAbuse) { | 225 | constructor (private readonly videoAbuse: VideoAbuse) { |
225 | super(videoAbuseKeysToKeep, 'abuse', videoAbuse) | 226 | super(videoAbuseKeysToKeep, 'abuse', videoAbuse) |
226 | } | 227 | } |
227 | } | 228 | } |
@@ -253,9 +254,12 @@ class CustomConfigAuditView extends EntityAuditView { | |||
253 | const infos: any = customConfig | 254 | const infos: any = customConfig |
254 | const resolutionsDict = infos.transcoding.resolutions | 255 | const resolutionsDict = infos.transcoding.resolutions |
255 | const resolutionsArray = [] | 256 | const resolutionsArray = [] |
256 | Object.entries(resolutionsDict).forEach(([resolution, isEnabled]) => { | 257 | |
257 | if (isEnabled) resolutionsArray.push(resolution) | 258 | Object.entries(resolutionsDict) |
258 | }) | 259 | .forEach(([ resolution, isEnabled ]) => { |
260 | if (isEnabled) resolutionsArray.push(resolution) | ||
261 | }) | ||
262 | |||
259 | Object.assign({}, infos, { transcoding: { resolutions: resolutionsArray } }) | 263 | Object.assign({}, infos, { transcoding: { resolutions: resolutionsArray } }) |
260 | super(customConfigKeysToKeep, 'config', infos) | 264 | super(customConfigKeysToKeep, 'config', infos) |
261 | } | 265 | } |
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 7e8252aa4..b1f5d9610 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts | |||
@@ -1,9 +1,11 @@ | |||
1 | /* eslint-disable no-useless-call */ | ||
2 | |||
1 | /* | 3 | /* |
2 | Different from 'utils' because we don't not import other PeerTube modules. | 4 | Different from 'utils' because we don't import other PeerTube modules. |
3 | Useful to avoid circular dependencies. | 5 | Useful to avoid circular dependencies. |
4 | */ | 6 | */ |
5 | 7 | ||
6 | import { createHash, HexBase64Latin1Encoding, pseudoRandomBytes } from 'crypto' | 8 | import { createHash, HexBase64Latin1Encoding, randomBytes } from 'crypto' |
7 | import { basename, isAbsolute, join, resolve } from 'path' | 9 | import { basename, isAbsolute, join, resolve } from 'path' |
8 | import * as pem from 'pem' | 10 | import * as pem from 'pem' |
9 | import { URL } from 'url' | 11 | import { URL } from 'url' |
@@ -22,31 +24,31 @@ const objectConverter = (oldObject: any, keyConverter: (e: string) => string, va | |||
22 | const newObject = {} | 24 | const newObject = {} |
23 | Object.keys(oldObject).forEach(oldKey => { | 25 | Object.keys(oldObject).forEach(oldKey => { |
24 | const newKey = keyConverter(oldKey) | 26 | const newKey = keyConverter(oldKey) |
25 | newObject[ newKey ] = objectConverter(oldObject[ oldKey ], keyConverter, valueConverter) | 27 | newObject[newKey] = objectConverter(oldObject[oldKey], keyConverter, valueConverter) |
26 | }) | 28 | }) |
27 | 29 | ||
28 | return newObject | 30 | return newObject |
29 | } | 31 | } |
30 | 32 | ||
31 | const timeTable = { | 33 | const timeTable = { |
32 | ms: 1, | 34 | ms: 1, |
33 | second: 1000, | 35 | second: 1000, |
34 | minute: 60000, | 36 | minute: 60000, |
35 | hour: 3600000, | 37 | hour: 3600000, |
36 | day: 3600000 * 24, | 38 | day: 3600000 * 24, |
37 | week: 3600000 * 24 * 7, | 39 | week: 3600000 * 24 * 7, |
38 | month: 3600000 * 24 * 30 | 40 | month: 3600000 * 24 * 30 |
39 | } | 41 | } |
40 | 42 | ||
41 | export function parseDurationToMs (duration: number | string): number { | 43 | export function parseDurationToMs (duration: number | string): number { |
42 | if (typeof duration === 'number') return duration | 44 | if (typeof duration === 'number') return duration |
43 | 45 | ||
44 | if (typeof duration === 'string') { | 46 | if (typeof duration === 'string') { |
45 | const split = duration.match(/^([\d\.,]+)\s?(\w+)$/) | 47 | const split = duration.match(/^([\d.,]+)\s?(\w+)$/) |
46 | 48 | ||
47 | if (split.length === 3) { | 49 | if (split.length === 3) { |
48 | const len = parseFloat(split[1]) | 50 | const len = parseFloat(split[1]) |
49 | let unit = split[2].replace(/s$/i,'').toLowerCase() | 51 | let unit = split[2].replace(/s$/i, '').toLowerCase() |
50 | if (unit === 'm') { | 52 | if (unit === 'm') { |
51 | unit = 'ms' | 53 | unit = 'ms' |
52 | } | 54 | } |
@@ -73,21 +75,21 @@ export function parseBytes (value: string | number): number { | |||
73 | 75 | ||
74 | if (value.match(tgm)) { | 76 | if (value.match(tgm)) { |
75 | match = value.match(tgm) | 77 | match = value.match(tgm) |
76 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 | 78 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + |
77 | + parseInt(match[2], 10) * 1024 * 1024 * 1024 | 79 | parseInt(match[2], 10) * 1024 * 1024 * 1024 + |
78 | + parseInt(match[3], 10) * 1024 * 1024 | 80 | parseInt(match[3], 10) * 1024 * 1024 |
79 | } else if (value.match(tg)) { | 81 | } else if (value.match(tg)) { |
80 | match = value.match(tg) | 82 | match = value.match(tg) |
81 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 | 83 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + |
82 | + parseInt(match[2], 10) * 1024 * 1024 * 1024 | 84 | parseInt(match[2], 10) * 1024 * 1024 * 1024 |
83 | } else if (value.match(tm)) { | 85 | } else if (value.match(tm)) { |
84 | match = value.match(tm) | 86 | match = value.match(tm) |
85 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 | 87 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 + |
86 | + parseInt(match[2], 10) * 1024 * 1024 | 88 | parseInt(match[2], 10) * 1024 * 1024 |
87 | } else if (value.match(gm)) { | 89 | } else if (value.match(gm)) { |
88 | match = value.match(gm) | 90 | match = value.match(gm) |
89 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 | 91 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 + |
90 | + parseInt(match[2], 10) * 1024 * 1024 | 92 | parseInt(match[2], 10) * 1024 * 1024 |
91 | } else if (value.match(t)) { | 93 | } else if (value.match(t)) { |
92 | match = value.match(t) | 94 | match = value.match(t) |
93 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 | 95 | return parseInt(match[1], 10) * 1024 * 1024 * 1024 * 1024 |
@@ -137,6 +139,7 @@ function getAppNumber () { | |||
137 | } | 139 | } |
138 | 140 | ||
139 | let rootPath: string | 141 | let rootPath: string |
142 | |||
140 | function root () { | 143 | function root () { |
141 | if (rootPath) return rootPath | 144 | if (rootPath) return rootPath |
142 | 145 | ||
@@ -163,7 +166,7 @@ function escapeHTML (stringParam) { | |||
163 | '=': '=' | 166 | '=': '=' |
164 | } | 167 | } |
165 | 168 | ||
166 | return String(stringParam).replace(/[&<>"'`=\/]/g, s => entityMap[s]) | 169 | return String(stringParam).replace(/[&<>"'`=/]/g, s => entityMap[s]) |
167 | } | 170 | } |
168 | 171 | ||
169 | function pageToStartAndCount (page: number, itemsPerPage: number) { | 172 | function pageToStartAndCount (page: number, itemsPerPage: number) { |
@@ -202,6 +205,7 @@ function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') | |||
202 | function execShell (command: string, options?: ExecOptions) { | 205 | function execShell (command: string, options?: ExecOptions) { |
203 | return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => { | 206 | return new Promise<{ err?: Error, stdout: string, stderr: string }>((res, rej) => { |
204 | exec(command, options, (err, stdout, stderr) => { | 207 | exec(command, options, (err, stdout, stderr) => { |
208 | // eslint-disable-next-line prefer-promise-reject-errors | ||
205 | if (err) return rej({ err, stdout, stderr }) | 209 | if (err) return rej({ err, stdout, stderr }) |
206 | 210 | ||
207 | return res({ stdout, stderr }) | 211 | return res({ stdout, stderr }) |
@@ -226,14 +230,6 @@ function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => | |||
226 | } | 230 | } |
227 | } | 231 | } |
228 | 232 | ||
229 | function promisify1WithVoid<T> (func: (arg: T, cb: (err: any) => void) => void): (arg: T) => Promise<void> { | ||
230 | return function promisified (arg: T): Promise<void> { | ||
231 | return new Promise<void>((resolve: () => void, reject: (err: any) => void) => { | ||
232 | func.apply(null, [ arg, (err: any) => err ? reject(err) : resolve() ]) | ||
233 | }) | ||
234 | } | ||
235 | } | ||
236 | |||
237 | function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> { | 233 | function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> { |
238 | return function promisified (arg1: T, arg2: U): Promise<A> { | 234 | return function promisified (arg1: T, arg2: U): Promise<A> { |
239 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { | 235 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { |
@@ -242,15 +238,7 @@ function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) | |||
242 | } | 238 | } |
243 | } | 239 | } |
244 | 240 | ||
245 | function promisify2WithVoid<T, U> (func: (arg1: T, arg2: U, cb: (err: any) => void) => void): (arg1: T, arg2: U) => Promise<void> { | 241 | const randomBytesPromise = promisify1<number, Buffer>(randomBytes) |
246 | return function promisified (arg1: T, arg2: U): Promise<void> { | ||
247 | return new Promise<void>((resolve: () => void, reject: (err: any) => void) => { | ||
248 | func.apply(null, [ arg1, arg2, (err: any) => err ? reject(err) : resolve() ]) | ||
249 | }) | ||
250 | } | ||
251 | } | ||
252 | |||
253 | const pseudoRandomBytesPromise = promisify1<number, Buffer>(pseudoRandomBytes) | ||
254 | const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) | 242 | const createPrivateKey = promisify1<number, { key: string }>(pem.createPrivateKey) |
255 | const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) | 243 | const getPublicKey = promisify1<string, { publicKey: string }>(pem.getPublicKey) |
256 | const execPromise2 = promisify2<string, any, string>(exec) | 244 | const execPromise2 = promisify2<string, any, string>(exec) |
@@ -280,7 +268,7 @@ export { | |||
280 | promisify1, | 268 | promisify1, |
281 | promisify2, | 269 | promisify2, |
282 | 270 | ||
283 | pseudoRandomBytesPromise, | 271 | randomBytesPromise, |
284 | createPrivateKey, | 272 | createPrivateKey, |
285 | getPublicKey, | 273 | getPublicKey, |
286 | execPromise2, | 274 | execPromise2, |
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index a407a9fec..749c50cb3 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts | |||
@@ -5,52 +5,52 @@ import { logger } from './logger' | |||
5 | const CACHE = { | 5 | const CACHE = { |
6 | 'https://w3id.org/security/v1': { | 6 | 'https://w3id.org/security/v1': { |
7 | '@context': { | 7 | '@context': { |
8 | 'id': '@id', | 8 | id: '@id', |
9 | 'type': '@type', | 9 | type: '@type', |
10 | 10 | ||
11 | 'dc': 'http://purl.org/dc/terms/', | 11 | dc: 'http://purl.org/dc/terms/', |
12 | 'sec': 'https://w3id.org/security#', | 12 | sec: 'https://w3id.org/security#', |
13 | 'xsd': 'http://www.w3.org/2001/XMLSchema#', | 13 | xsd: 'http://www.w3.org/2001/XMLSchema#', |
14 | 14 | ||
15 | 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', | 15 | EcdsaKoblitzSignature2016: 'sec:EcdsaKoblitzSignature2016', |
16 | 'Ed25519Signature2018': 'sec:Ed25519Signature2018', | 16 | Ed25519Signature2018: 'sec:Ed25519Signature2018', |
17 | 'EncryptedMessage': 'sec:EncryptedMessage', | 17 | EncryptedMessage: 'sec:EncryptedMessage', |
18 | 'GraphSignature2012': 'sec:GraphSignature2012', | 18 | GraphSignature2012: 'sec:GraphSignature2012', |
19 | 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', | 19 | LinkedDataSignature2015: 'sec:LinkedDataSignature2015', |
20 | 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', | 20 | LinkedDataSignature2016: 'sec:LinkedDataSignature2016', |
21 | 'CryptographicKey': 'sec:Key', | 21 | CryptographicKey: 'sec:Key', |
22 | 22 | ||
23 | 'authenticationTag': 'sec:authenticationTag', | 23 | authenticationTag: 'sec:authenticationTag', |
24 | 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', | 24 | canonicalizationAlgorithm: 'sec:canonicalizationAlgorithm', |
25 | 'cipherAlgorithm': 'sec:cipherAlgorithm', | 25 | cipherAlgorithm: 'sec:cipherAlgorithm', |
26 | 'cipherData': 'sec:cipherData', | 26 | cipherData: 'sec:cipherData', |
27 | 'cipherKey': 'sec:cipherKey', | 27 | cipherKey: 'sec:cipherKey', |
28 | 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, | 28 | created: { '@id': 'dc:created', '@type': 'xsd:dateTime' }, |
29 | 'creator': { '@id': 'dc:creator', '@type': '@id' }, | 29 | creator: { '@id': 'dc:creator', '@type': '@id' }, |
30 | 'digestAlgorithm': 'sec:digestAlgorithm', | 30 | digestAlgorithm: 'sec:digestAlgorithm', |
31 | 'digestValue': 'sec:digestValue', | 31 | digestValue: 'sec:digestValue', |
32 | 'domain': 'sec:domain', | 32 | domain: 'sec:domain', |
33 | 'encryptionKey': 'sec:encryptionKey', | 33 | encryptionKey: 'sec:encryptionKey', |
34 | 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, | 34 | expiration: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, |
35 | 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, | 35 | expires: { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, |
36 | 'initializationVector': 'sec:initializationVector', | 36 | initializationVector: 'sec:initializationVector', |
37 | 'iterationCount': 'sec:iterationCount', | 37 | iterationCount: 'sec:iterationCount', |
38 | 'nonce': 'sec:nonce', | 38 | nonce: 'sec:nonce', |
39 | 'normalizationAlgorithm': 'sec:normalizationAlgorithm', | 39 | normalizationAlgorithm: 'sec:normalizationAlgorithm', |
40 | 'owner': { '@id': 'sec:owner', '@type': '@id' }, | 40 | owner: { '@id': 'sec:owner', '@type': '@id' }, |
41 | 'password': 'sec:password', | 41 | password: 'sec:password', |
42 | 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, | 42 | privateKey: { '@id': 'sec:privateKey', '@type': '@id' }, |
43 | 'privateKeyPem': 'sec:privateKeyPem', | 43 | privateKeyPem: 'sec:privateKeyPem', |
44 | 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, | 44 | publicKey: { '@id': 'sec:publicKey', '@type': '@id' }, |
45 | 'publicKeyBase58': 'sec:publicKeyBase58', | 45 | publicKeyBase58: 'sec:publicKeyBase58', |
46 | 'publicKeyPem': 'sec:publicKeyPem', | 46 | publicKeyPem: 'sec:publicKeyPem', |
47 | 'publicKeyWif': 'sec:publicKeyWif', | 47 | publicKeyWif: 'sec:publicKeyWif', |
48 | 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, | 48 | publicKeyService: { '@id': 'sec:publicKeyService', '@type': '@id' }, |
49 | 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, | 49 | revoked: { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, |
50 | 'salt': 'sec:salt', | 50 | salt: 'sec:salt', |
51 | 'signature': 'sec:signature', | 51 | signature: 'sec:signature', |
52 | 'signatureAlgorithm': 'sec:signingAlgorithm', | 52 | signatureAlgorithm: 'sec:signingAlgorithm', |
53 | 'signatureValue': 'sec:signatureValue' | 53 | signatureValue: 'sec:signatureValue' |
54 | } | 54 | } |
55 | } | 55 | } |
56 | } | 56 | } |
@@ -60,12 +60,12 @@ const nodeDocumentLoader = jsonld.documentLoaders.node() | |||
60 | const lru = new AsyncLRU({ | 60 | const lru = new AsyncLRU({ |
61 | max: 10, | 61 | max: 10, |
62 | load: (url, cb) => { | 62 | load: (url, cb) => { |
63 | if (CACHE[ url ] !== undefined) { | 63 | if (CACHE[url] !== undefined) { |
64 | logger.debug('Using cache for JSON-LD %s.', url) | 64 | logger.debug('Using cache for JSON-LD %s.', url) |
65 | 65 | ||
66 | return cb(null, { | 66 | return cb(null, { |
67 | contextUrl: null, | 67 | contextUrl: null, |
68 | document: CACHE[ url ], | 68 | document: CACHE[url], |
69 | documentUrl: url | 69 | documentUrl: url |
70 | }) | 70 | }) |
71 | } | 71 | } |
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index fa58e163f..2f44522a5 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts | |||
@@ -6,7 +6,7 @@ import { isHostValid } from '../servers' | |||
6 | import { peertubeTruncate } from '@server/helpers/core-utils' | 6 | import { peertubeTruncate } from '@server/helpers/core-utils' |
7 | 7 | ||
8 | function isActorEndpointsObjectValid (endpointObject: any) { | 8 | function isActorEndpointsObjectValid (endpointObject: any) { |
9 | if (endpointObject && endpointObject.sharedInbox) { | 9 | if (endpointObject?.sharedInbox) { |
10 | return isActivityPubUrlValid(endpointObject.sharedInbox) | 10 | return isActivityPubUrlValid(endpointObject.sharedInbox) |
11 | } | 11 | } |
12 | 12 | ||
@@ -28,7 +28,7 @@ function isActorPublicKeyValid (publicKey: string) { | |||
28 | return exists(publicKey) && | 28 | return exists(publicKey) && |
29 | typeof publicKey === 'string' && | 29 | typeof publicKey === 'string' && |
30 | publicKey.startsWith('-----BEGIN PUBLIC KEY-----') && | 30 | publicKey.startsWith('-----BEGIN PUBLIC KEY-----') && |
31 | publicKey.indexOf('-----END PUBLIC KEY-----') !== -1 && | 31 | publicKey.includes('-----END PUBLIC KEY-----') && |
32 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) | 32 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) |
33 | } | 33 | } |
34 | 34 | ||
@@ -43,7 +43,7 @@ function isActorPrivateKeyValid (privateKey: string) { | |||
43 | typeof privateKey === 'string' && | 43 | typeof privateKey === 'string' && |
44 | privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') && | 44 | privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') && |
45 | // Sometimes there is a \n at the end, so just assert the string contains the end mark | 45 | // Sometimes there is a \n at the end, so just assert the string contains the end mark |
46 | privateKey.indexOf('-----END RSA PRIVATE KEY-----') !== -1 && | 46 | privateKey.includes('-----END RSA PRIVATE KEY-----') && |
47 | validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY) | 47 | validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY) |
48 | } | 48 | } |
49 | 49 | ||
@@ -101,8 +101,6 @@ function normalizeActor (actor: any) { | |||
101 | actor.summary = null | 101 | actor.summary = null |
102 | } | 102 | } |
103 | } | 103 | } |
104 | |||
105 | return | ||
106 | } | 104 | } |
107 | 105 | ||
108 | function isValidActorHandle (handle: string) { | 106 | function isValidActorHandle (handle: string) { |
diff --git a/server/helpers/custom-validators/activitypub/cache-file.ts b/server/helpers/custom-validators/activitypub/cache-file.ts index 21d5c53ca..c5b3b4d9f 100644 --- a/server/helpers/custom-validators/activitypub/cache-file.ts +++ b/server/helpers/custom-validators/activitypub/cache-file.ts | |||
@@ -6,7 +6,7 @@ import { CacheFileObject } from '../../../../shared/models/activitypub/objects' | |||
6 | function isCacheFileObjectValid (object: CacheFileObject) { | 6 | function isCacheFileObjectValid (object: CacheFileObject) { |
7 | return exists(object) && | 7 | return exists(object) && |
8 | object.type === 'CacheFile' && | 8 | object.type === 'CacheFile' && |
9 | isDateValid(object.expires) && | 9 | (object.expires === null || isDateValid(object.expires)) && |
10 | isActivityPubUrlValid(object.object) && | 10 | isActivityPubUrlValid(object.object) && |
11 | (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url)) | 11 | (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url)) |
12 | } | 12 | } |
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts index aa3c246b5..ea852c491 100644 --- a/server/helpers/custom-validators/activitypub/video-comments.ts +++ b/server/helpers/custom-validators/activitypub/video-comments.ts | |||
@@ -48,8 +48,6 @@ function normalizeComment (comment: any) { | |||
48 | if (typeof comment.url === 'object') comment.url = comment.url.href || comment.url.url | 48 | if (typeof comment.url === 'object') comment.url = comment.url.href || comment.url.url |
49 | else comment.url = comment.id | 49 | else comment.url = comment.id |
50 | } | 50 | } |
51 | |||
52 | return | ||
53 | } | 51 | } |
54 | 52 | ||
55 | function isCommentTypeValid (comment: any): boolean { | 53 | function isCommentTypeValid (comment: any): boolean { |
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index fe94bd58a..876cc7f50 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts | |||
@@ -13,6 +13,7 @@ import { | |||
13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | 13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
14 | import { VideoState } from '../../../../shared/models/videos' | 14 | import { VideoState } from '../../../../shared/models/videos' |
15 | import { logger } from '@server/helpers/logger' | 15 | import { logger } from '@server/helpers/logger' |
16 | import { ActivityVideoFileMetadataObject } from '@shared/models' | ||
16 | 17 | ||
17 | function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { | 18 | function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { |
18 | return isBaseActivityValid(activity, 'Update') && | 19 | return isBaseActivityValid(activity, 'Update') && |
@@ -51,11 +52,16 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
51 | logger.debug('Video has invalid captions', { video }) | 52 | logger.debug('Video has invalid captions', { video }) |
52 | return false | 53 | return false |
53 | } | 54 | } |
55 | if (!setValidRemoteIcon(video)) { | ||
56 | logger.debug('Video has invalid icons', { video }) | ||
57 | return false | ||
58 | } | ||
54 | 59 | ||
55 | // Default attributes | 60 | // Default attributes |
56 | if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED | 61 | if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED |
57 | if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false | 62 | if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false |
58 | if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true | 63 | if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true |
64 | if (!isBooleanValid(video.commentsEnabled)) video.commentsEnabled = false | ||
59 | 65 | ||
60 | return isActivityPubUrlValid(video.id) && | 66 | return isActivityPubUrlValid(video.id) && |
61 | isVideoNameValid(video.name) && | 67 | isVideoNameValid(video.name) && |
@@ -72,7 +78,6 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
72 | isDateValid(video.updated) && | 78 | isDateValid(video.updated) && |
73 | (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) && | 79 | (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) && |
74 | (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && | 80 | (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && |
75 | isRemoteVideoIconValid(video.icon) && | ||
76 | video.url.length !== 0 && | 81 | video.url.length !== 0 && |
77 | video.attributedTo.length !== 0 | 82 | video.attributedTo.length !== 0 |
78 | } | 83 | } |
@@ -80,19 +85,19 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
80 | function isRemoteVideoUrlValid (url: any) { | 85 | function isRemoteVideoUrlValid (url: any) { |
81 | return url.type === 'Link' && | 86 | return url.type === 'Link' && |
82 | ( | 87 | ( |
83 | ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.indexOf(url.mediaType) !== -1 && | 88 | ACTIVITY_PUB.URL_MIME_TYPES.VIDEO.includes(url.mediaType) && |
84 | isActivityPubUrlValid(url.href) && | 89 | isActivityPubUrlValid(url.href) && |
85 | validator.isInt(url.height + '', { min: 0 }) && | 90 | validator.isInt(url.height + '', { min: 0 }) && |
86 | validator.isInt(url.size + '', { min: 0 }) && | 91 | validator.isInt(url.size + '', { min: 0 }) && |
87 | (!url.fps || validator.isInt(url.fps + '', { min: -1 })) | 92 | (!url.fps || validator.isInt(url.fps + '', { min: -1 })) |
88 | ) || | 93 | ) || |
89 | ( | 94 | ( |
90 | ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.indexOf(url.mediaType) !== -1 && | 95 | ACTIVITY_PUB.URL_MIME_TYPES.TORRENT.includes(url.mediaType) && |
91 | isActivityPubUrlValid(url.href) && | 96 | isActivityPubUrlValid(url.href) && |
92 | validator.isInt(url.height + '', { min: 0 }) | 97 | validator.isInt(url.height + '', { min: 0 }) |
93 | ) || | 98 | ) || |
94 | ( | 99 | ( |
95 | ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mediaType) !== -1 && | 100 | ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.includes(url.mediaType) && |
96 | validator.isLength(url.href, { min: 5 }) && | 101 | validator.isLength(url.href, { min: 5 }) && |
97 | validator.isInt(url.height + '', { min: 0 }) | 102 | validator.isInt(url.height + '', { min: 0 }) |
98 | ) || | 103 | ) || |
@@ -100,7 +105,15 @@ function isRemoteVideoUrlValid (url: any) { | |||
100 | (url.mediaType || url.mimeType) === 'application/x-mpegURL' && | 105 | (url.mediaType || url.mimeType) === 'application/x-mpegURL' && |
101 | isActivityPubUrlValid(url.href) && | 106 | isActivityPubUrlValid(url.href) && |
102 | isArray(url.tag) | 107 | isArray(url.tag) |
103 | ) | 108 | ) || |
109 | isAPVideoFileMetadataObject(url) | ||
110 | } | ||
111 | |||
112 | function isAPVideoFileMetadataObject (url: any): url is ActivityVideoFileMetadataObject { | ||
113 | return url && | ||
114 | url.type === 'Link' && | ||
115 | url.mediaType === 'application/json' && | ||
116 | isArray(url.rel) && url.rel.includes('metadata') | ||
104 | } | 117 | } |
105 | 118 | ||
106 | // --------------------------------------------------------------------------- | 119 | // --------------------------------------------------------------------------- |
@@ -109,7 +122,8 @@ export { | |||
109 | sanitizeAndCheckVideoTorrentUpdateActivity, | 122 | sanitizeAndCheckVideoTorrentUpdateActivity, |
110 | isRemoteStringIdentifierValid, | 123 | isRemoteStringIdentifierValid, |
111 | sanitizeAndCheckVideoTorrentObject, | 124 | sanitizeAndCheckVideoTorrentObject, |
112 | isRemoteVideoUrlValid | 125 | isRemoteVideoUrlValid, |
126 | isAPVideoFileMetadataObject | ||
113 | } | 127 | } |
114 | 128 | ||
115 | // --------------------------------------------------------------------------- | 129 | // --------------------------------------------------------------------------- |
@@ -131,6 +145,8 @@ function setValidRemoteCaptions (video: any) { | |||
131 | if (Array.isArray(video.subtitleLanguage) === false) return false | 145 | if (Array.isArray(video.subtitleLanguage) === false) return false |
132 | 146 | ||
133 | video.subtitleLanguage = video.subtitleLanguage.filter(caption => { | 147 | video.subtitleLanguage = video.subtitleLanguage.filter(caption => { |
148 | if (!isActivityPubUrlValid(caption.url)) caption.url = null | ||
149 | |||
134 | return isRemoteStringIdentifierValid(caption) | 150 | return isRemoteStringIdentifierValid(caption) |
135 | }) | 151 | }) |
136 | 152 | ||
@@ -149,12 +165,19 @@ function isRemoteVideoContentValid (mediaType: string, content: string) { | |||
149 | return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content) | 165 | return mediaType === 'text/markdown' && isVideoTruncatedDescriptionValid(content) |
150 | } | 166 | } |
151 | 167 | ||
152 | function isRemoteVideoIconValid (icon: any) { | 168 | function setValidRemoteIcon (video: any) { |
153 | return icon.type === 'Image' && | 169 | if (video.icon && !isArray(video.icon)) video.icon = [ video.icon ] |
154 | isActivityPubUrlValid(icon.url) && | 170 | if (!video.icon) video.icon = [] |
155 | icon.mediaType === 'image/jpeg' && | 171 | |
156 | validator.isInt(icon.width + '', { min: 0 }) && | 172 | video.icon = video.icon.filter(icon => { |
157 | validator.isInt(icon.height + '', { min: 0 }) | 173 | return icon.type === 'Image' && |
174 | isActivityPubUrlValid(icon.url) && | ||
175 | icon.mediaType === 'image/jpeg' && | ||
176 | validator.isInt(icon.width + '', { min: 0 }) && | ||
177 | validator.isInt(icon.height + '', { min: 0 }) | ||
178 | }) | ||
179 | |||
180 | return video.icon.length !== 0 | ||
158 | } | 181 | } |
159 | 182 | ||
160 | function setValidRemoteVideoUrls (video: any) { | 183 | function setValidRemoteVideoUrls (video: any) { |
diff --git a/server/helpers/custom-validators/feeds.ts b/server/helpers/custom-validators/feeds.ts index 638e814f0..fa35a7da6 100644 --- a/server/helpers/custom-validators/feeds.ts +++ b/server/helpers/custom-validators/feeds.ts | |||
@@ -13,7 +13,7 @@ function isValidRSSFeed (value: string) { | |||
13 | 'atom1' | 13 | 'atom1' |
14 | ] | 14 | ] |
15 | 15 | ||
16 | return feedExtensions.indexOf(value) !== -1 | 16 | return feedExtensions.includes(value) |
17 | } | 17 | } |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/custom-validators/logs.ts b/server/helpers/custom-validators/logs.ts index 30d0ce262..0f266ed3b 100644 --- a/server/helpers/custom-validators/logs.ts +++ b/server/helpers/custom-validators/logs.ts | |||
@@ -4,7 +4,7 @@ import { LogLevel } from '../../../shared/models/server/log-level.type' | |||
4 | const logLevels: LogLevel[] = [ 'debug', 'info', 'warn', 'error' ] | 4 | const logLevels: LogLevel[] = [ 'debug', 'info', 'warn', 'error' ] |
5 | 5 | ||
6 | function isValidLogLevel (value: any) { | 6 | function isValidLogLevel (value: any) { |
7 | return exists(value) && logLevels.indexOf(value) !== -1 | 7 | return exists(value) && logLevels.includes(value) |
8 | } | 8 | } |
9 | 9 | ||
10 | // --------------------------------------------------------------------------- | 10 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index 89149b3e0..cf32201c4 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -94,13 +94,13 @@ function isFileValid ( | |||
94 | if (isArray(files)) return optional | 94 | if (isArray(files)) return optional |
95 | 95 | ||
96 | // Should have a file | 96 | // Should have a file |
97 | const fileArray = files[ field ] | 97 | const fileArray = files[field] |
98 | if (!fileArray || fileArray.length === 0) { | 98 | if (!fileArray || fileArray.length === 0) { |
99 | return optional | 99 | return optional |
100 | } | 100 | } |
101 | 101 | ||
102 | // The file should exist | 102 | // The file should exist |
103 | const file = fileArray[ 0 ] | 103 | const file = fileArray[0] |
104 | if (!file || !file.originalname) return false | 104 | if (!file || !file.originalname) return false |
105 | 105 | ||
106 | // Check size | 106 | // Check size |
diff --git a/server/helpers/custom-validators/plugins.ts b/server/helpers/custom-validators/plugins.ts index 3af72547b..d2fc03936 100644 --- a/server/helpers/custom-validators/plugins.ts +++ b/server/helpers/custom-validators/plugins.ts | |||
@@ -14,7 +14,7 @@ function isPluginTypeValid (value: any) { | |||
14 | function isPluginNameValid (value: string) { | 14 | function isPluginNameValid (value: string) { |
15 | return exists(value) && | 15 | return exists(value) && |
16 | validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) && | 16 | validator.isLength(value, PLUGINS_CONSTRAINTS_FIELDS.NAME) && |
17 | validator.matches(value, /^[a-z\-]+$/) | 17 | validator.matches(value, /^[a-z-0-9]+$/) |
18 | } | 18 | } |
19 | 19 | ||
20 | function isNpmPluginNameValid (value: string) { | 20 | function isNpmPluginNameValid (value: string) { |
@@ -146,8 +146,8 @@ function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginT | |||
146 | } | 146 | } |
147 | 147 | ||
148 | function isLibraryCodeValid (library: any) { | 148 | function isLibraryCodeValid (library: any) { |
149 | return typeof library.register === 'function' | 149 | return typeof library.register === 'function' && |
150 | && typeof library.unregister === 'function' | 150 | typeof library.unregister === 'function' |
151 | } | 151 | } |
152 | 152 | ||
153 | export { | 153 | export { |
diff --git a/server/helpers/custom-validators/user-notifications.ts b/server/helpers/custom-validators/user-notifications.ts index 5a4d10504..8a33b895b 100644 --- a/server/helpers/custom-validators/user-notifications.ts +++ b/server/helpers/custom-validators/user-notifications.ts | |||
@@ -9,7 +9,8 @@ function isUserNotificationTypeValid (value: any) { | |||
9 | 9 | ||
10 | function isUserNotificationSettingValid (value: any) { | 10 | function isUserNotificationSettingValid (value: any) { |
11 | return exists(value) && | 11 | return exists(value) && |
12 | validator.isInt('' + value) && ( | 12 | validator.isInt('' + value) && |
13 | ( | ||
13 | value === UserNotificationSettingValue.NONE || | 14 | value === UserNotificationSettingValue.NONE || |
14 | value === UserNotificationSettingValue.WEB || | 15 | value === UserNotificationSettingValue.WEB || |
15 | value === UserNotificationSettingValue.EMAIL || | 16 | value === UserNotificationSettingValue.EMAIL || |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index b4d5751e7..d6e91ad35 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -3,6 +3,7 @@ import { UserRole } from '../../../shared' | |||
3 | import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants' | 3 | import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants' |
4 | import { exists, isArray, isBooleanValid, isFileValid } from './misc' | 4 | import { exists, isArray, isBooleanValid, isFileValid } from './misc' |
5 | import { values } from 'lodash' | 5 | import { values } from 'lodash' |
6 | import { isEmailEnabled } from '../../initializers/config' | ||
6 | 7 | ||
7 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS | 8 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS |
8 | 9 | ||
@@ -10,6 +11,13 @@ function isUserPasswordValid (value: string) { | |||
10 | return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD) | 11 | return validator.isLength(value, USERS_CONSTRAINTS_FIELDS.PASSWORD) |
11 | } | 12 | } |
12 | 13 | ||
14 | function isUserPasswordValidOrEmpty (value: string) { | ||
15 | // Empty password is only possible if emailing is enabled. | ||
16 | if (value === '') return isEmailEnabled() | ||
17 | |||
18 | return isUserPasswordValid(value) | ||
19 | } | ||
20 | |||
13 | function isUserVideoQuotaValid (value: string) { | 21 | function isUserVideoQuotaValid (value: string) { |
14 | return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA) | 22 | return exists(value) && validator.isInt(value + '', USERS_CONSTRAINTS_FIELDS.VIDEO_QUOTA) |
15 | } | 23 | } |
@@ -38,7 +46,7 @@ function isUserEmailVerifiedValid (value: any) { | |||
38 | 46 | ||
39 | const nsfwPolicies = values(NSFW_POLICY_TYPES) | 47 | const nsfwPolicies = values(NSFW_POLICY_TYPES) |
40 | function isUserNSFWPolicyValid (value: any) { | 48 | function isUserNSFWPolicyValid (value: any) { |
41 | return exists(value) && nsfwPolicies.indexOf(value) !== -1 | 49 | return exists(value) && nsfwPolicies.includes(value) |
42 | } | 50 | } |
43 | 51 | ||
44 | function isUserWebTorrentEnabledValid (value: any) { | 52 | function isUserWebTorrentEnabledValid (value: any) { |
@@ -103,6 +111,7 @@ export { | |||
103 | isUserVideosHistoryEnabledValid, | 111 | isUserVideosHistoryEnabledValid, |
104 | isUserBlockedValid, | 112 | isUserBlockedValid, |
105 | isUserPasswordValid, | 113 | isUserPasswordValid, |
114 | isUserPasswordValidOrEmpty, | ||
106 | isUserVideoLanguages, | 115 | isUserVideoLanguages, |
107 | isUserBlockedReasonValid, | 116 | isUserBlockedReasonValid, |
108 | isUserRoleValid, | 117 | isUserRoleValid, |
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts index a9478c76a..05e11b1c6 100644 --- a/server/helpers/custom-validators/video-abuses.ts +++ b/server/helpers/custom-validators/video-abuses.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { Response } from 'express' | ||
2 | import validator from 'validator' | 1 | import validator from 'validator' |
2 | |||
3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | 3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' |
4 | import { exists } from './misc' | 4 | import { exists } from './misc' |
5 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 5 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' |
6 | 6 | ||
7 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES | 7 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_ABUSES |
8 | 8 | ||
@@ -15,7 +15,14 @@ function isVideoAbuseModerationCommentValid (value: string) { | |||
15 | } | 15 | } |
16 | 16 | ||
17 | function isVideoAbuseStateValid (value: string) { | 17 | function isVideoAbuseStateValid (value: string) { |
18 | return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined | 18 | return exists(value) && VIDEO_ABUSE_STATES[value] !== undefined |
19 | } | ||
20 | |||
21 | function isAbuseVideoIsValid (value: VideoAbuseVideoIs) { | ||
22 | return exists(value) && ( | ||
23 | value === 'deleted' || | ||
24 | value === 'blacklisted' | ||
25 | ) | ||
19 | } | 26 | } |
20 | 27 | ||
21 | // --------------------------------------------------------------------------- | 28 | // --------------------------------------------------------------------------- |
@@ -23,5 +30,6 @@ function isVideoAbuseStateValid (value: string) { | |||
23 | export { | 30 | export { |
24 | isVideoAbuseStateValid, | 31 | isVideoAbuseStateValid, |
25 | isVideoAbuseReasonValid, | 32 | isVideoAbuseReasonValid, |
33 | isAbuseVideoIsValid, | ||
26 | isVideoAbuseModerationCommentValid | 34 | isVideoAbuseModerationCommentValid |
27 | } | 35 | } |
diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts index d06eb3695..528edf60c 100644 --- a/server/helpers/custom-validators/video-captions.ts +++ b/server/helpers/custom-validators/video-captions.ts | |||
@@ -2,13 +2,13 @@ import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initialize | |||
2 | import { exists, isFileValid } from './misc' | 2 | import { exists, isFileValid } from './misc' |
3 | 3 | ||
4 | function isVideoCaptionLanguageValid (value: any) { | 4 | function isVideoCaptionLanguageValid (value: any) { |
5 | return exists(value) && VIDEO_LANGUAGES[ value ] !== undefined | 5 | return exists(value) && VIDEO_LANGUAGES[value] !== undefined |
6 | } | 6 | } |
7 | 7 | ||
8 | const videoCaptionTypes = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT) | 8 | const videoCaptionTypesRegex = Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT) |
9 | .concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream >< | 9 | .concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream |
10 | .map(m => `(${m})`) | 10 | .map(m => `(${m})`) |
11 | const videoCaptionTypesRegex = videoCaptionTypes.join('|') | 11 | .join('|') |
12 | function isVideoCaptionFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) { | 12 | function isVideoCaptionFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], field: string) { |
13 | return isFileValid(files, videoCaptionTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) | 13 | return isFileValid(files, videoCaptionTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) |
14 | } | 14 | } |
diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts index ffad482b4..33a1fa8ab 100644 --- a/server/helpers/custom-validators/video-imports.ts +++ b/server/helpers/custom-validators/video-imports.ts | |||
@@ -20,11 +20,13 @@ function isVideoImportTargetUrlValid (url: string) { | |||
20 | } | 20 | } |
21 | 21 | ||
22 | function isVideoImportStateValid (value: any) { | 22 | function isVideoImportStateValid (value: any) { |
23 | return exists(value) && VIDEO_IMPORT_STATES[ value ] !== undefined | 23 | return exists(value) && VIDEO_IMPORT_STATES[value] !== undefined |
24 | } | 24 | } |
25 | 25 | ||
26 | const videoTorrentImportTypes = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT).map(m => `(${m})`) | 26 | const videoTorrentImportRegex = Object.keys(MIMETYPES.TORRENT.MIMETYPE_EXT) |
27 | const videoTorrentImportRegex = videoTorrentImportTypes.join('|') | 27 | .concat([ 'application/octet-stream' ]) // MacOS sends application/octet-stream |
28 | .map(m => `(${m})`) | ||
29 | .join('|') | ||
28 | function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { | 30 | function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[]) { |
29 | return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) | 31 | return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) |
30 | } | 32 | } |
diff --git a/server/helpers/custom-validators/video-playlists.ts b/server/helpers/custom-validators/video-playlists.ts index 4bb8384ab..180018fc5 100644 --- a/server/helpers/custom-validators/video-playlists.ts +++ b/server/helpers/custom-validators/video-playlists.ts | |||
@@ -1,8 +1,6 @@ | |||
1 | import { exists } from './misc' | 1 | import { exists } from './misc' |
2 | import validator from 'validator' | 2 | import validator from 'validator' |
3 | import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants' | 3 | import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants' |
4 | import * as express from 'express' | ||
5 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
6 | 4 | ||
7 | const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS | 5 | const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS |
8 | 6 | ||
@@ -15,7 +13,7 @@ function isVideoPlaylistDescriptionValid (value: any) { | |||
15 | } | 13 | } |
16 | 14 | ||
17 | function isVideoPlaylistPrivacyValid (value: number) { | 15 | function isVideoPlaylistPrivacyValid (value: number) { |
18 | return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[ value ] !== undefined | 16 | return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[value] !== undefined |
19 | } | 17 | } |
20 | 18 | ||
21 | function isVideoPlaylistTimestampValid (value: any) { | 19 | function isVideoPlaylistTimestampValid (value: any) { |
@@ -23,7 +21,7 @@ function isVideoPlaylistTimestampValid (value: any) { | |||
23 | } | 21 | } |
24 | 22 | ||
25 | function isVideoPlaylistTypeValid (value: any) { | 23 | function isVideoPlaylistTypeValid (value: any) { |
26 | return exists(value) && VIDEO_PLAYLIST_TYPES[ value ] !== undefined | 24 | return exists(value) && VIDEO_PLAYLIST_TYPES[value] !== undefined |
27 | } | 25 | } |
28 | 26 | ||
29 | // --------------------------------------------------------------------------- | 27 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/custom-validators/video-redundancies.ts b/server/helpers/custom-validators/video-redundancies.ts new file mode 100644 index 000000000..50a559c4f --- /dev/null +++ b/server/helpers/custom-validators/video-redundancies.ts | |||
@@ -0,0 +1,12 @@ | |||
1 | import { exists } from './misc' | ||
2 | |||
3 | function isVideoRedundancyTarget (value: any) { | ||
4 | return exists(value) && | ||
5 | (value === 'my-videos' || value === 'remote-videos') | ||
6 | } | ||
7 | |||
8 | // --------------------------------------------------------------------------- | ||
9 | |||
10 | export { | ||
11 | isVideoRedundancyTarget | ||
12 | } | ||
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index a9e859e54..60e8075f6 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -20,15 +20,15 @@ function isVideoFilterValid (filter: VideoFilter) { | |||
20 | } | 20 | } |
21 | 21 | ||
22 | function isVideoCategoryValid (value: any) { | 22 | function isVideoCategoryValid (value: any) { |
23 | return value === null || VIDEO_CATEGORIES[ value ] !== undefined | 23 | return value === null || VIDEO_CATEGORIES[value] !== undefined |
24 | } | 24 | } |
25 | 25 | ||
26 | function isVideoStateValid (value: any) { | 26 | function isVideoStateValid (value: any) { |
27 | return exists(value) && VIDEO_STATES[ value ] !== undefined | 27 | return exists(value) && VIDEO_STATES[value] !== undefined |
28 | } | 28 | } |
29 | 29 | ||
30 | function isVideoLicenceValid (value: any) { | 30 | function isVideoLicenceValid (value: any) { |
31 | return value === null || VIDEO_LICENCES[ value ] !== undefined | 31 | return value === null || VIDEO_LICENCES[value] !== undefined |
32 | } | 32 | } |
33 | 33 | ||
34 | function isVideoLanguageValid (value: any) { | 34 | function isVideoLanguageValid (value: any) { |
@@ -73,7 +73,7 @@ function isVideoViewsValid (value: string) { | |||
73 | } | 73 | } |
74 | 74 | ||
75 | function isVideoRatingTypeValid (value: string) { | 75 | function isVideoRatingTypeValid (value: string) { |
76 | return value === 'none' || values(VIDEO_RATE_TYPES).indexOf(value as VideoRateType) !== -1 | 76 | return value === 'none' || values(VIDEO_RATE_TYPES).includes(value as VideoRateType) |
77 | } | 77 | } |
78 | 78 | ||
79 | function isVideoFileExtnameValid (value: string) { | 79 | function isVideoFileExtnameValid (value: string) { |
@@ -98,7 +98,7 @@ function isVideoImage (files: { [ fieldname: string ]: Express.Multer.File[] } | | |||
98 | } | 98 | } |
99 | 99 | ||
100 | function isVideoPrivacyValid (value: number) { | 100 | function isVideoPrivacyValid (value: number) { |
101 | return VIDEO_PRIVACIES[ value ] !== undefined | 101 | return VIDEO_PRIVACIES[value] !== undefined |
102 | } | 102 | } |
103 | 103 | ||
104 | function isScheduleVideoUpdatePrivacyValid (value: number) { | 104 | function isScheduleVideoUpdatePrivacyValid (value: number) { |
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index 9bf6d85a8..f46812977 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts | |||
@@ -12,7 +12,7 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { | |||
12 | if (paramNSFW === 'false') return false | 12 | if (paramNSFW === 'false') return false |
13 | if (paramNSFW === 'both') return undefined | 13 | if (paramNSFW === 'both') return undefined |
14 | 14 | ||
15 | if (res && res.locals.oauth) { | 15 | if (res?.locals.oauth) { |
16 | const user = res.locals.oauth.token.User | 16 | const user = res.locals.oauth.token.User |
17 | 17 | ||
18 | // User does not want NSFW videos | 18 | // User does not want NSFW videos |
@@ -28,7 +28,7 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { | |||
28 | return null | 28 | return null |
29 | } | 29 | } |
30 | 30 | ||
31 | function cleanUpReqFiles (req: { files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[] }) { | 31 | function cleanUpReqFiles (req: { files: { [fieldname: string]: Express.Multer.File[] } | Express.Multer.File[] }) { |
32 | const files = req.files | 32 | const files = req.files |
33 | 33 | ||
34 | if (!files) return | 34 | if (!files) return |
@@ -39,7 +39,7 @@ function cleanUpReqFiles (req: { files: { [ fieldname: string ]: Express.Multer. | |||
39 | } | 39 | } |
40 | 40 | ||
41 | for (const key of Object.keys(files)) { | 41 | for (const key of Object.keys(files)) { |
42 | const file = files[ key ] | 42 | const file = files[key] |
43 | 43 | ||
44 | if (isArray(file)) file.forEach(f => deleteFileAsync(f.path)) | 44 | if (isArray(file)) file.forEach(f => deleteFileAsync(f.path)) |
45 | else deleteFileAsync(file.path) | 45 | else deleteFileAsync(file.path) |
@@ -65,18 +65,18 @@ function badRequest (req: express.Request, res: express.Response) { | |||
65 | 65 | ||
66 | function createReqFiles ( | 66 | function createReqFiles ( |
67 | fieldNames: string[], | 67 | fieldNames: string[], |
68 | mimeTypes: { [ id: string ]: string }, | 68 | mimeTypes: { [id: string]: string }, |
69 | destinations: { [ fieldName: string ]: string } | 69 | destinations: { [fieldName: string]: string } |
70 | ) { | 70 | ) { |
71 | const storage = multer.diskStorage({ | 71 | const storage = multer.diskStorage({ |
72 | destination: (req, file, cb) => { | 72 | destination: (req, file, cb) => { |
73 | cb(null, destinations[ file.fieldname ]) | 73 | cb(null, destinations[file.fieldname]) |
74 | }, | 74 | }, |
75 | 75 | ||
76 | filename: async (req, file, cb) => { | 76 | filename: async (req, file, cb) => { |
77 | let extension: string | 77 | let extension: string |
78 | const fileExtension = extname(file.originalname) | 78 | const fileExtension = extname(file.originalname) |
79 | const extensionFromMimetype = mimeTypes[ file.mimetype ] | 79 | const extensionFromMimetype = mimeTypes[file.mimetype] |
80 | 80 | ||
81 | // Take the file extension if we don't understand the mime type | 81 | // Take the file extension if we don't understand the mime type |
82 | // We have the OGG/OGV exception too because firefox sends a bad mime type when sending an OGG file | 82 | // We have the OGG/OGV exception too because firefox sends a bad mime type when sending an OGG file |
@@ -99,7 +99,7 @@ function createReqFiles ( | |||
99 | } | 99 | } |
100 | }) | 100 | }) |
101 | 101 | ||
102 | let fields: { name: string, maxCount: number }[] = [] | 102 | const fields: { name: string, maxCount: number }[] = [] |
103 | for (const fieldName of fieldNames) { | 103 | for (const fieldName of fieldNames) { |
104 | fields.push({ | 104 | fields.push({ |
105 | name: fieldName, | 105 | name: fieldName, |
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 00c32e99a..557fb5e3a 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -1,12 +1,78 @@ | |||
1 | import * as ffmpeg from 'fluent-ffmpeg' | 1 | import * as ffmpeg from 'fluent-ffmpeg' |
2 | import { dirname, join } from 'path' | 2 | import { dirname, join } from 'path' |
3 | import { getTargetBitrate, getMaxBitrate, VideoResolution } from '../../shared/models/videos' | 3 | import { getMaxBitrate, getTargetBitrate, VideoResolution } from '../../shared/models/videos' |
4 | import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' | 4 | import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' |
5 | import { processImage } from './image-utils' | 5 | import { processImage } from './image-utils' |
6 | import { logger } from './logger' | 6 | import { logger } from './logger' |
7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' | 7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' |
8 | import { readFile, remove, writeFile } from 'fs-extra' | 8 | import { readFile, remove, writeFile } from 'fs-extra' |
9 | import { CONFIG } from '../initializers/config' | 9 | import { CONFIG } from '../initializers/config' |
10 | import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata' | ||
11 | |||
12 | /** | ||
13 | * A toolbox to play with audio | ||
14 | */ | ||
15 | namespace audio { | ||
16 | export const get = (videoPath: string) => { | ||
17 | // without position, ffprobe considers the last input only | ||
18 | // we make it consider the first input only | ||
19 | // if you pass a file path to pos, then ffprobe acts on that file directly | ||
20 | return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => { | ||
21 | |||
22 | function parseFfprobe (err: any, data: ffmpeg.FfprobeData) { | ||
23 | if (err) return rej(err) | ||
24 | |||
25 | if ('streams' in data) { | ||
26 | const audioStream = data.streams.find(stream => stream['codec_type'] === 'audio') | ||
27 | if (audioStream) { | ||
28 | return res({ | ||
29 | absolutePath: data.format.filename, | ||
30 | audioStream | ||
31 | }) | ||
32 | } | ||
33 | } | ||
34 | |||
35 | return res({ absolutePath: data.format.filename }) | ||
36 | } | ||
37 | |||
38 | return ffmpeg.ffprobe(videoPath, parseFfprobe) | ||
39 | }) | ||
40 | } | ||
41 | |||
42 | export namespace bitrate { | ||
43 | const baseKbitrate = 384 | ||
44 | |||
45 | const toBits = (kbits: number) => kbits * 8000 | ||
46 | |||
47 | export const aac = (bitrate: number): number => { | ||
48 | switch (true) { | ||
49 | case bitrate > toBits(baseKbitrate): | ||
50 | return baseKbitrate | ||
51 | |||
52 | default: | ||
53 | return -1 // we interpret it as a signal to copy the audio stream as is | ||
54 | } | ||
55 | } | ||
56 | |||
57 | export const mp3 = (bitrate: number): number => { | ||
58 | /* | ||
59 | a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac. | ||
60 | That's why, when using aac, we can go to lower kbit/sec. The equivalences | ||
61 | made here are not made to be accurate, especially with good mp3 encoders. | ||
62 | */ | ||
63 | switch (true) { | ||
64 | case bitrate <= toBits(192): | ||
65 | return 128 | ||
66 | |||
67 | case bitrate <= toBits(384): | ||
68 | return 256 | ||
69 | |||
70 | default: | ||
71 | return baseKbitrate | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | } | ||
10 | 76 | ||
11 | function computeResolutionsToTranscode (videoFileHeight: number) { | 77 | function computeResolutionsToTranscode (videoFileHeight: number) { |
12 | const resolutionsEnabled: number[] = [] | 78 | const resolutionsEnabled: number[] = [] |
@@ -24,7 +90,7 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
24 | ] | 90 | ] |
25 | 91 | ||
26 | for (const resolution of resolutions) { | 92 | for (const resolution of resolutions) { |
27 | if (configResolutions[ resolution + 'p' ] === true && videoFileHeight > resolution) { | 93 | if (configResolutions[resolution + 'p'] === true && videoFileHeight > resolution) { |
28 | resolutionsEnabled.push(resolution) | 94 | resolutionsEnabled.push(resolution) |
29 | } | 95 | } |
30 | } | 96 | } |
@@ -48,9 +114,9 @@ async function getVideoStreamCodec (path: string) { | |||
48 | const videoCodec = videoStream.codec_tag_string | 114 | const videoCodec = videoStream.codec_tag_string |
49 | 115 | ||
50 | const baseProfileMatrix = { | 116 | const baseProfileMatrix = { |
51 | 'High': '6400', | 117 | High: '6400', |
52 | 'Main': '4D40', | 118 | Main: '4D40', |
53 | 'Baseline': '42E0' | 119 | Baseline: '42E0' |
54 | } | 120 | } |
55 | 121 | ||
56 | let baseProfile = baseProfileMatrix[videoStream.profile] | 122 | let baseProfile = baseProfileMatrix[videoStream.profile] |
@@ -59,7 +125,8 @@ async function getVideoStreamCodec (path: string) { | |||
59 | baseProfile = baseProfileMatrix['High'] // Fallback | 125 | baseProfile = baseProfileMatrix['High'] // Fallback |
60 | } | 126 | } |
61 | 127 | ||
62 | const level = videoStream.level.toString(16) | 128 | let level = videoStream.level.toString(16) |
129 | if (level.length === 1) level = `0${level}` | ||
63 | 130 | ||
64 | return `${videoCodec}.${baseProfile}${level}` | 131 | return `${videoCodec}.${baseProfile}${level}` |
65 | } | 132 | } |
@@ -91,7 +158,7 @@ async function getVideoFileFPS (path: string) { | |||
91 | if (videoStream === null) return 0 | 158 | if (videoStream === null) return 0 |
92 | 159 | ||
93 | for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { | 160 | for (const key of [ 'avg_frame_rate', 'r_frame_rate' ]) { |
94 | const valuesText: string = videoStream[ key ] | 161 | const valuesText: string = videoStream[key] |
95 | if (!valuesText) continue | 162 | if (!valuesText) continue |
96 | 163 | ||
97 | const [ frames, seconds ] = valuesText.split('/') | 164 | const [ frames, seconds ] = valuesText.split('/') |
@@ -104,24 +171,26 @@ async function getVideoFileFPS (path: string) { | |||
104 | return 0 | 171 | return 0 |
105 | } | 172 | } |
106 | 173 | ||
107 | async function getVideoFileBitrate (path: string) { | 174 | async function getMetadataFromFile <T> (path: string, cb = metadata => metadata) { |
108 | return new Promise<number>((res, rej) => { | 175 | return new Promise<T>((res, rej) => { |
109 | ffmpeg.ffprobe(path, (err, metadata) => { | 176 | ffmpeg.ffprobe(path, (err, metadata) => { |
110 | if (err) return rej(err) | 177 | if (err) return rej(err) |
111 | 178 | ||
112 | return res(metadata.format.bit_rate) | 179 | return res(cb(new VideoFileMetadata(metadata))) |
113 | }) | 180 | }) |
114 | }) | 181 | }) |
115 | } | 182 | } |
116 | 183 | ||
184 | async function getVideoFileBitrate (path: string) { | ||
185 | return getMetadataFromFile<number>(path, metadata => metadata.format.bit_rate) | ||
186 | } | ||
187 | |||
117 | function getDurationFromVideoFile (path: string) { | 188 | function getDurationFromVideoFile (path: string) { |
118 | return new Promise<number>((res, rej) => { | 189 | return getMetadataFromFile<number>(path, metadata => Math.floor(metadata.format.duration)) |
119 | ffmpeg.ffprobe(path, (err, metadata) => { | 190 | } |
120 | if (err) return rej(err) | ||
121 | 191 | ||
122 | return res(Math.floor(metadata.format.duration)) | 192 | function getVideoStreamFromFile (path: string) { |
123 | }) | 193 | return getMetadataFromFile<any>(path, metadata => metadata.streams.find(s => s.codec_type === 'video') || null) |
124 | }) | ||
125 | } | 194 | } |
126 | 195 | ||
127 | async function generateImageFromVideoFile (fromPath: string, folder: string, imageName: string, size: { width: number, height: number }) { | 196 | async function generateImageFromVideoFile (fromPath: string, folder: string, imageName: string, size: { width: number, height: number }) { |
@@ -191,7 +260,8 @@ interface OnlyAudioTranscodeOptions extends BaseTranscodeOptions { | |||
191 | type: 'only-audio' | 260 | type: 'only-audio' |
192 | } | 261 | } |
193 | 262 | ||
194 | type TranscodeOptions = HLSTranscodeOptions | 263 | type TranscodeOptions = |
264 | HLSTranscodeOptions | ||
195 | | VideoTranscodeOptions | 265 | | VideoTranscodeOptions |
196 | | MergeAudioTranscodeOptions | 266 | | MergeAudioTranscodeOptions |
197 | | OnlyAudioTranscodeOptions | 267 | | OnlyAudioTranscodeOptions |
@@ -204,13 +274,13 @@ function transcode (options: TranscodeOptions) { | |||
204 | .output(options.outputPath) | 274 | .output(options.outputPath) |
205 | 275 | ||
206 | if (options.type === 'quick-transcode') { | 276 | if (options.type === 'quick-transcode') { |
207 | command = await buildQuickTranscodeCommand(command) | 277 | command = buildQuickTranscodeCommand(command) |
208 | } else if (options.type === 'hls') { | 278 | } else if (options.type === 'hls') { |
209 | command = await buildHLSCommand(command, options) | 279 | command = await buildHLSCommand(command, options) |
210 | } else if (options.type === 'merge-audio') { | 280 | } else if (options.type === 'merge-audio') { |
211 | command = await buildAudioMergeCommand(command, options) | 281 | command = await buildAudioMergeCommand(command, options) |
212 | } else if (options.type === 'only-audio') { | 282 | } else if (options.type === 'only-audio') { |
213 | command = await buildOnlyAudioCommand(command, options) | 283 | command = buildOnlyAudioCommand(command, options) |
214 | } else { | 284 | } else { |
215 | command = await buildx264Command(command, options) | 285 | command = await buildx264Command(command, options) |
216 | } | 286 | } |
@@ -247,22 +317,27 @@ async function canDoQuickTranscode (path: string): Promise<boolean> { | |||
247 | 317 | ||
248 | // check video params | 318 | // check video params |
249 | if (videoStream == null) return false | 319 | if (videoStream == null) return false |
250 | if (videoStream[ 'codec_name' ] !== 'h264') return false | 320 | if (videoStream['codec_name'] !== 'h264') return false |
251 | if (videoStream[ 'pix_fmt' ] !== 'yuv420p') return false | 321 | if (videoStream['pix_fmt'] !== 'yuv420p') return false |
252 | if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false | 322 | if (fps < VIDEO_TRANSCODING_FPS.MIN || fps > VIDEO_TRANSCODING_FPS.MAX) return false |
253 | if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false | 323 | if (bitRate > getMaxBitrate(resolution.videoFileResolution, fps, VIDEO_TRANSCODING_FPS)) return false |
254 | 324 | ||
255 | // check audio params (if audio stream exists) | 325 | // check audio params (if audio stream exists) |
256 | if (parsedAudio.audioStream) { | 326 | if (parsedAudio.audioStream) { |
257 | if (parsedAudio.audioStream[ 'codec_name' ] !== 'aac') return false | 327 | if (parsedAudio.audioStream['codec_name'] !== 'aac') return false |
258 | 328 | ||
259 | const maxAudioBitrate = audio.bitrate[ 'aac' ](parsedAudio.audioStream[ 'bit_rate' ]) | 329 | const maxAudioBitrate = audio.bitrate['aac'](parsedAudio.audioStream['bit_rate']) |
260 | if (maxAudioBitrate !== -1 && parsedAudio.audioStream[ 'bit_rate' ] > maxAudioBitrate) return false | 330 | if (maxAudioBitrate !== -1 && parsedAudio.audioStream['bit_rate'] > maxAudioBitrate) return false |
261 | } | 331 | } |
262 | 332 | ||
263 | return true | 333 | return true |
264 | } | 334 | } |
265 | 335 | ||
336 | function getClosestFramerateStandard (fps: number, type: 'HD_STANDARD' | 'STANDARD'): number { | ||
337 | return VIDEO_TRANSCODING_FPS[type].slice(0) | ||
338 | .sort((a, b) => fps % a - fps % b)[0] | ||
339 | } | ||
340 | |||
266 | // --------------------------------------------------------------------------- | 341 | // --------------------------------------------------------------------------- |
267 | 342 | ||
268 | export { | 343 | export { |
@@ -270,6 +345,7 @@ export { | |||
270 | getAudioStreamCodec, | 345 | getAudioStreamCodec, |
271 | getVideoStreamSize, | 346 | getVideoStreamSize, |
272 | getVideoFileResolution, | 347 | getVideoFileResolution, |
348 | getMetadataFromFile, | ||
273 | getDurationFromVideoFile, | 349 | getDurationFromVideoFile, |
274 | generateImageFromVideoFile, | 350 | generateImageFromVideoFile, |
275 | TranscodeOptions, | 351 | TranscodeOptions, |
@@ -286,13 +362,14 @@ export { | |||
286 | 362 | ||
287 | async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { | 363 | async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { |
288 | let fps = await getVideoFileFPS(options.inputPath) | 364 | let fps = await getVideoFileFPS(options.inputPath) |
289 | // On small/medium resolutions, limit FPS | ||
290 | if ( | 365 | if ( |
366 | // On small/medium resolutions, limit FPS | ||
291 | options.resolution !== undefined && | 367 | options.resolution !== undefined && |
292 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | 368 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && |
293 | fps > VIDEO_TRANSCODING_FPS.AVERAGE | 369 | fps > VIDEO_TRANSCODING_FPS.AVERAGE |
294 | ) { | 370 | ) { |
295 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | 371 | // Get closest standard framerate by modulo: downsampling has to be done to a divisor of the nominal fps value |
372 | fps = getClosestFramerateStandard(fps, 'STANDARD') | ||
296 | } | 373 | } |
297 | 374 | ||
298 | command = await presetH264(command, options.inputPath, options.resolution, fps) | 375 | command = await presetH264(command, options.inputPath, options.resolution, fps) |
@@ -305,7 +382,7 @@ async function buildx264Command (command: ffmpeg.FfmpegCommand, options: Transco | |||
305 | 382 | ||
306 | if (fps) { | 383 | if (fps) { |
307 | // Hard FPS limits | 384 | // Hard FPS limits |
308 | if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX | 385 | if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = getClosestFramerateStandard(fps, 'HD_STANDARD') |
309 | else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN | 386 | else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN |
310 | 387 | ||
311 | command = command.withFPS(fps) | 388 | command = command.withFPS(fps) |
@@ -327,14 +404,14 @@ async function buildAudioMergeCommand (command: ffmpeg.FfmpegCommand, options: M | |||
327 | return command | 404 | return command |
328 | } | 405 | } |
329 | 406 | ||
330 | async function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, options: OnlyAudioTranscodeOptions) { | 407 | function buildOnlyAudioCommand (command: ffmpeg.FfmpegCommand, options: OnlyAudioTranscodeOptions) { |
331 | command = await presetOnlyAudio(command) | 408 | command = presetOnlyAudio(command) |
332 | 409 | ||
333 | return command | 410 | return command |
334 | } | 411 | } |
335 | 412 | ||
336 | async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { | 413 | function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { |
337 | command = await presetCopy(command) | 414 | command = presetCopy(command) |
338 | 415 | ||
339 | command = command.outputOption('-map_metadata -1') // strip all metadata | 416 | command = command.outputOption('-map_metadata -1') // strip all metadata |
340 | .outputOption('-movflags faststart') | 417 | .outputOption('-movflags faststart') |
@@ -345,7 +422,8 @@ async function buildQuickTranscodeCommand (command: ffmpeg.FfmpegCommand) { | |||
345 | async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) { | 422 | async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: HLSTranscodeOptions) { |
346 | const videoPath = getHLSVideoPath(options) | 423 | const videoPath = getHLSVideoPath(options) |
347 | 424 | ||
348 | if (options.copyCodecs) command = await presetCopy(command) | 425 | if (options.copyCodecs) command = presetCopy(command) |
426 | else if (options.resolution === VideoResolution.H_NOVIDEO) command = presetOnlyAudio(command) | ||
349 | else command = await buildx264Command(command, options) | 427 | else command = await buildx264Command(command, options) |
350 | 428 | ||
351 | command = command.outputOption('-hls_time 4') | 429 | command = command.outputOption('-hls_time 4') |
@@ -378,17 +456,6 @@ async function fixHLSPlaylistIfNeeded (options: TranscodeOptions) { | |||
378 | await writeFile(options.outputPath, newContent) | 456 | await writeFile(options.outputPath, newContent) |
379 | } | 457 | } |
380 | 458 | ||
381 | function getVideoStreamFromFile (path: string) { | ||
382 | return new Promise<any>((res, rej) => { | ||
383 | ffmpeg.ffprobe(path, (err, metadata) => { | ||
384 | if (err) return rej(err) | ||
385 | |||
386 | const videoStream = metadata.streams.find(s => s.codec_type === 'video') | ||
387 | return res(videoStream || null) | ||
388 | }) | ||
389 | }) | ||
390 | } | ||
391 | |||
392 | /** | 459 | /** |
393 | * A slightly customised version of the 'veryfast' x264 preset | 460 | * A slightly customised version of the 'veryfast' x264 preset |
394 | * | 461 | * |
@@ -413,71 +480,6 @@ async function presetH264VeryFast (command: ffmpeg.FfmpegCommand, input: string, | |||
413 | } | 480 | } |
414 | 481 | ||
415 | /** | 482 | /** |
416 | * A toolbox to play with audio | ||
417 | */ | ||
418 | namespace audio { | ||
419 | export const get = (videoPath: string) => { | ||
420 | // without position, ffprobe considers the last input only | ||
421 | // we make it consider the first input only | ||
422 | // if you pass a file path to pos, then ffprobe acts on that file directly | ||
423 | return new Promise<{ absolutePath: string, audioStream?: any }>((res, rej) => { | ||
424 | |||
425 | function parseFfprobe (err: any, data: ffmpeg.FfprobeData) { | ||
426 | if (err) return rej(err) | ||
427 | |||
428 | if ('streams' in data) { | ||
429 | const audioStream = data.streams.find(stream => stream[ 'codec_type' ] === 'audio') | ||
430 | if (audioStream) { | ||
431 | return res({ | ||
432 | absolutePath: data.format.filename, | ||
433 | audioStream | ||
434 | }) | ||
435 | } | ||
436 | } | ||
437 | |||
438 | return res({ absolutePath: data.format.filename }) | ||
439 | } | ||
440 | |||
441 | return ffmpeg.ffprobe(videoPath, parseFfprobe) | ||
442 | }) | ||
443 | } | ||
444 | |||
445 | export namespace bitrate { | ||
446 | const baseKbitrate = 384 | ||
447 | |||
448 | const toBits = (kbits: number) => kbits * 8000 | ||
449 | |||
450 | export const aac = (bitrate: number): number => { | ||
451 | switch (true) { | ||
452 | case bitrate > toBits(baseKbitrate): | ||
453 | return baseKbitrate | ||
454 | |||
455 | default: | ||
456 | return -1 // we interpret it as a signal to copy the audio stream as is | ||
457 | } | ||
458 | } | ||
459 | |||
460 | export const mp3 = (bitrate: number): number => { | ||
461 | /* | ||
462 | a 192kbit/sec mp3 doesn't hold as much information as a 192kbit/sec aac. | ||
463 | That's why, when using aac, we can go to lower kbit/sec. The equivalences | ||
464 | made here are not made to be accurate, especially with good mp3 encoders. | ||
465 | */ | ||
466 | switch (true) { | ||
467 | case bitrate <= toBits(192): | ||
468 | return 128 | ||
469 | |||
470 | case bitrate <= toBits(384): | ||
471 | return 256 | ||
472 | |||
473 | default: | ||
474 | return baseKbitrate | ||
475 | } | ||
476 | } | ||
477 | } | ||
478 | } | ||
479 | |||
480 | /** | ||
481 | * Standard profile, with variable bitrate audio and faststart. | 483 | * Standard profile, with variable bitrate audio and faststart. |
482 | * | 484 | * |
483 | * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel | 485 | * As for the audio, quality '5' is the highest and ensures 96-112kbps/channel |
@@ -507,10 +509,10 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolut | |||
507 | // of course this is far from perfect, but it might save some space in the end | 509 | // of course this is far from perfect, but it might save some space in the end |
508 | localCommand = localCommand.audioCodec('aac') | 510 | localCommand = localCommand.audioCodec('aac') |
509 | 511 | ||
510 | const audioCodecName = parsedAudio.audioStream[ 'codec_name' ] | 512 | const audioCodecName = parsedAudio.audioStream['codec_name'] |
511 | 513 | ||
512 | if (audio.bitrate[ audioCodecName ]) { | 514 | if (audio.bitrate[audioCodecName]) { |
513 | const bitrate = audio.bitrate[ audioCodecName ](parsedAudio.audioStream[ 'bit_rate' ]) | 515 | const bitrate = audio.bitrate[audioCodecName](parsedAudio.audioStream['bit_rate']) |
514 | if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate) | 516 | if (bitrate !== undefined && bitrate !== -1) localCommand = localCommand.audioBitrate(bitrate) |
515 | } | 517 | } |
516 | } | 518 | } |
@@ -531,14 +533,14 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, input: string, resolut | |||
531 | return localCommand | 533 | return localCommand |
532 | } | 534 | } |
533 | 535 | ||
534 | async function presetCopy (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { | 536 | function presetCopy (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand { |
535 | return command | 537 | return command |
536 | .format('mp4') | 538 | .format('mp4') |
537 | .videoCodec('copy') | 539 | .videoCodec('copy') |
538 | .audioCodec('copy') | 540 | .audioCodec('copy') |
539 | } | 541 | } |
540 | 542 | ||
541 | async function presetOnlyAudio (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { | 543 | function presetOnlyAudio (command: ffmpeg.FfmpegCommand): ffmpeg.FfmpegCommand { |
542 | return command | 544 | return command |
543 | .format('mp4') | 545 | .format('mp4') |
544 | .audioCodec('copy') | 546 | .audioCodec('copy') |
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts index 395417612..9553f70e8 100644 --- a/server/helpers/logger.ts +++ b/server/helpers/logger.ts | |||
@@ -5,7 +5,7 @@ import * as winston from 'winston' | |||
5 | import { FileTransportOptions } from 'winston/lib/winston/transports' | 5 | import { FileTransportOptions } from 'winston/lib/winston/transports' |
6 | import { CONFIG } from '../initializers/config' | 6 | import { CONFIG } from '../initializers/config' |
7 | import { omit } from 'lodash' | 7 | import { omit } from 'lodash' |
8 | import { LOG_FILENAME } from '@server/initializers/constants' | 8 | import { LOG_FILENAME } from '../initializers/constants' |
9 | 9 | ||
10 | const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | 10 | const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT |
11 | 11 | ||
@@ -27,7 +27,7 @@ function getLoggerReplacer () { | |||
27 | if (value instanceof Error) { | 27 | if (value instanceof Error) { |
28 | const error = {} | 28 | const error = {} |
29 | 29 | ||
30 | Object.getOwnPropertyNames(value).forEach(key => error[ key ] = value[ key ]) | 30 | Object.getOwnPropertyNames(value).forEach(key => { error[key] = value[key] }) |
31 | 31 | ||
32 | return error | 32 | return error |
33 | } | 33 | } |
@@ -54,9 +54,11 @@ const jsonLoggerFormat = winston.format.printf(info => { | |||
54 | const timestampFormatter = winston.format.timestamp({ | 54 | const timestampFormatter = winston.format.timestamp({ |
55 | format: 'YYYY-MM-DD HH:mm:ss.SSS' | 55 | format: 'YYYY-MM-DD HH:mm:ss.SSS' |
56 | }) | 56 | }) |
57 | const labelFormatter = winston.format.label({ | 57 | const labelFormatter = (suffix?: string) => { |
58 | label | 58 | return winston.format.label({ |
59 | }) | 59 | label: suffix ? `${label} ${suffix}` : label |
60 | }) | ||
61 | } | ||
60 | 62 | ||
61 | const fileLoggerOptions: FileTransportOptions = { | 63 | const fileLoggerOptions: FileTransportOptions = { |
62 | filename: path.join(CONFIG.STORAGE.LOG_DIR, LOG_FILENAME), | 64 | filename: path.join(CONFIG.STORAGE.LOG_DIR, LOG_FILENAME), |
@@ -72,25 +74,29 @@ if (CONFIG.LOG.ROTATION.ENABLED) { | |||
72 | fileLoggerOptions.maxFiles = CONFIG.LOG.ROTATION.MAX_FILES | 74 | fileLoggerOptions.maxFiles = CONFIG.LOG.ROTATION.MAX_FILES |
73 | } | 75 | } |
74 | 76 | ||
75 | const logger = winston.createLogger({ | 77 | const logger = buildLogger() |
76 | level: CONFIG.LOG.LEVEL, | 78 | |
77 | format: winston.format.combine( | 79 | function buildLogger (labelSuffix?: string) { |
78 | labelFormatter, | 80 | return winston.createLogger({ |
79 | winston.format.splat() | 81 | level: CONFIG.LOG.LEVEL, |
80 | ), | 82 | format: winston.format.combine( |
81 | transports: [ | 83 | labelFormatter(labelSuffix), |
82 | new winston.transports.File(fileLoggerOptions), | 84 | winston.format.splat() |
83 | new winston.transports.Console({ | 85 | ), |
84 | handleExceptions: true, | 86 | transports: [ |
85 | format: winston.format.combine( | 87 | new winston.transports.File(fileLoggerOptions), |
86 | timestampFormatter, | 88 | new winston.transports.Console({ |
87 | winston.format.colorize(), | 89 | handleExceptions: true, |
88 | consoleLoggerFormat | 90 | format: winston.format.combine( |
89 | ) | 91 | timestampFormatter, |
90 | }) | 92 | winston.format.colorize(), |
91 | ], | 93 | consoleLoggerFormat |
92 | exitOnError: true | 94 | ) |
93 | }) | 95 | }) |
96 | ], | ||
97 | exitOnError: true | ||
98 | }) | ||
99 | } | ||
94 | 100 | ||
95 | function bunyanLogFactory (level: string) { | 101 | function bunyanLogFactory (level: string) { |
96 | return function () { | 102 | return function () { |
@@ -98,19 +104,20 @@ function bunyanLogFactory (level: string) { | |||
98 | let args: any[] = [] | 104 | let args: any[] = [] |
99 | args.concat(arguments) | 105 | args.concat(arguments) |
100 | 106 | ||
101 | if (arguments[ 0 ] instanceof Error) { | 107 | if (arguments[0] instanceof Error) { |
102 | meta = arguments[ 0 ].toString() | 108 | meta = arguments[0].toString() |
103 | args = Array.prototype.slice.call(arguments, 1) | 109 | args = Array.prototype.slice.call(arguments, 1) |
104 | args.push(meta) | 110 | args.push(meta) |
105 | } else if (typeof (args[ 0 ]) !== 'string') { | 111 | } else if (typeof (args[0]) !== 'string') { |
106 | meta = arguments[ 0 ] | 112 | meta = arguments[0] |
107 | args = Array.prototype.slice.call(arguments, 1) | 113 | args = Array.prototype.slice.call(arguments, 1) |
108 | args.push(meta) | 114 | args.push(meta) |
109 | } | 115 | } |
110 | 116 | ||
111 | logger[ level ].apply(logger, args) | 117 | logger[level].apply(logger, args) |
112 | } | 118 | } |
113 | } | 119 | } |
120 | |||
114 | const bunyanLogger = { | 121 | const bunyanLogger = { |
115 | trace: bunyanLogFactory('debug'), | 122 | trace: bunyanLogFactory('debug'), |
116 | debug: bunyanLogFactory('debug'), | 123 | debug: bunyanLogFactory('debug'), |
@@ -122,6 +129,7 @@ const bunyanLogger = { | |||
122 | // --------------------------------------------------------------------------- | 129 | // --------------------------------------------------------------------------- |
123 | 130 | ||
124 | export { | 131 | export { |
132 | buildLogger, | ||
125 | timestampFormatter, | 133 | timestampFormatter, |
126 | labelFormatter, | 134 | labelFormatter, |
127 | consoleLoggerFormat, | 135 | consoleLoggerFormat, |
diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/video-abuses.ts index 8a1d3d618..97a5724b6 100644 --- a/server/helpers/middlewares/video-abuses.ts +++ b/server/helpers/middlewares/video-abuses.ts | |||
@@ -1,9 +1,17 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 2 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
3 | import { fetchVideo } from '../video' | ||
3 | 4 | ||
4 | async function doesVideoAbuseExist (abuseIdArg: number | string, videoId: number, res: Response) { | 5 | async function doesVideoAbuseExist (abuseIdArg: number | string, videoUUID: string, res: Response) { |
5 | const abuseId = parseInt(abuseIdArg + '', 10) | 6 | const abuseId = parseInt(abuseIdArg + '', 10) |
6 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) | 7 | let videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, null, videoUUID) |
8 | |||
9 | if (!videoAbuse) { | ||
10 | const userId = res.locals.oauth?.token.User.id | ||
11 | const video = await fetchVideo(videoUUID, 'all', userId) | ||
12 | |||
13 | if (video) videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, video.id) | ||
14 | } | ||
7 | 15 | ||
8 | if (videoAbuse === null) { | 16 | if (videoAbuse === null) { |
9 | res.status(404) | 17 | res.status(404) |
diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts index 74f529804..a0bbcdb21 100644 --- a/server/helpers/middlewares/videos.ts +++ b/server/helpers/middlewares/videos.ts | |||
@@ -2,7 +2,17 @@ import { Response } from 'express' | |||
2 | import { fetchVideo, VideoFetchType } from '../video' | 2 | import { fetchVideo, VideoFetchType } from '../video' |
3 | import { UserRight } from '../../../shared/models/users' | 3 | import { UserRight } from '../../../shared/models/users' |
4 | import { VideoChannelModel } from '../../models/video/video-channel' | 4 | import { VideoChannelModel } from '../../models/video/video-channel' |
5 | import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models' | 5 | import { |
6 | MUser, | ||
7 | MUserAccountId, | ||
8 | MVideoAccountLight, | ||
9 | MVideoFullLight, | ||
10 | MVideoIdThumbnail, | ||
11 | MVideoImmutable, | ||
12 | MVideoThumbnail, | ||
13 | MVideoWithRights | ||
14 | } from '@server/typings/models' | ||
15 | import { VideoFileModel } from '@server/models/video/video-file' | ||
6 | 16 | ||
7 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { | 17 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { |
8 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | 18 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined |
@@ -22,8 +32,12 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi | |||
22 | res.locals.videoAll = video as MVideoFullLight | 32 | res.locals.videoAll = video as MVideoFullLight |
23 | break | 33 | break |
24 | 34 | ||
35 | case 'only-immutable-attributes': | ||
36 | res.locals.onlyImmutableVideo = video as MVideoImmutable | ||
37 | break | ||
38 | |||
25 | case 'id': | 39 | case 'id': |
26 | res.locals.videoId = video | 40 | res.locals.videoId = video as MVideoIdThumbnail |
27 | break | 41 | break |
28 | 42 | ||
29 | case 'only-video': | 43 | case 'only-video': |
@@ -38,6 +52,18 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi | |||
38 | return true | 52 | return true |
39 | } | 53 | } |
40 | 54 | ||
55 | async function doesVideoFileOfVideoExist (id: number, videoIdOrUUID: number | string, res: Response) { | ||
56 | if (!await VideoFileModel.doesVideoExistForVideoFile(id, videoIdOrUUID)) { | ||
57 | res.status(404) | ||
58 | .json({ error: 'VideoFile matching Video not found' }) | ||
59 | .end() | ||
60 | |||
61 | return false | ||
62 | } | ||
63 | |||
64 | return true | ||
65 | } | ||
66 | |||
41 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { | 67 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { |
42 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | 68 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { |
43 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | 69 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) |
@@ -94,5 +120,6 @@ function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: | |||
94 | export { | 120 | export { |
95 | doesVideoChannelOfAccountExist, | 121 | doesVideoChannelOfAccountExist, |
96 | doesVideoExist, | 122 | doesVideoExist, |
123 | doesVideoFileOfVideoExist, | ||
97 | checkUserCanManageVideo | 124 | checkUserCanManageVideo |
98 | } | 125 | } |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 89c0ab151..394e97fd5 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -5,7 +5,6 @@ import { jsonld } from './custom-jsonld-signature' | |||
5 | import { logger } from './logger' | 5 | import { logger } from './logger' |
6 | import { cloneDeep } from 'lodash' | 6 | import { cloneDeep } from 'lodash' |
7 | import { createSign, createVerify } from 'crypto' | 7 | import { createSign, createVerify } from 'crypto' |
8 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' | ||
9 | import * as bcrypt from 'bcrypt' | 8 | import * as bcrypt from 'bcrypt' |
10 | import { MActor } from '../typings/models' | 9 | import { MActor } from '../typings/models' |
11 | 10 | ||
@@ -104,12 +103,19 @@ async function signJsonLDObject (byActor: MActor, data: any) { | |||
104 | return Object.assign(data, { signature }) | 103 | return Object.assign(data, { signature }) |
105 | } | 104 | } |
106 | 105 | ||
106 | function buildDigest (body: any) { | ||
107 | const rawBody = typeof body === 'string' ? body : JSON.stringify(body) | ||
108 | |||
109 | return 'SHA-256=' + sha256(rawBody, 'base64') | ||
110 | } | ||
111 | |||
107 | // --------------------------------------------------------------------------- | 112 | // --------------------------------------------------------------------------- |
108 | 113 | ||
109 | export { | 114 | export { |
110 | isHTTPSignatureDigestValid, | 115 | isHTTPSignatureDigestValid, |
111 | parseHTTPSignature, | 116 | parseHTTPSignature, |
112 | isHTTPSignatureVerified, | 117 | isHTTPSignatureVerified, |
118 | buildDigest, | ||
113 | isJsonLDSignatureVerified, | 119 | isJsonLDSignatureVerified, |
114 | comparePassword, | 120 | comparePassword, |
115 | createPrivateAndPublicKeys, | 121 | createPrivateAndPublicKeys, |
diff --git a/server/helpers/regexp.ts b/server/helpers/regexp.ts index 2336654b0..cfc2be488 100644 --- a/server/helpers/regexp.ts +++ b/server/helpers/regexp.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | // Thanks to https://regex101.com | 1 | // Thanks to https://regex101.com |
2 | function regexpCapture (str: string, regex: RegExp, maxIterations = 100) { | 2 | function regexpCapture (str: string, regex: RegExp, maxIterations = 100) { |
3 | const result: RegExpExecArray[] = [] | ||
3 | let m: RegExpExecArray | 4 | let m: RegExpExecArray |
4 | let i = 0 | 5 | let i = 0 |
5 | let result: RegExpExecArray[] = [] | ||
6 | 6 | ||
7 | // tslint:disable:no-conditional-assignment | 7 | // tslint:disable:no-conditional-assignment |
8 | while ((m = regex.exec(str)) !== null && i < maxIterations) { | 8 | while ((m = regex.exec(str)) !== null && i < maxIterations) { |
diff --git a/server/helpers/register-ts-paths.ts b/server/helpers/register-ts-paths.ts index e8db369e3..eec7fed3e 100644 --- a/server/helpers/register-ts-paths.ts +++ b/server/helpers/register-ts-paths.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { resolve } from 'path' | 1 | import { resolve } from 'path' |
2 | const tsConfigPaths = require('tsconfig-paths') | 2 | import tsConfigPaths = require('tsconfig-paths') |
3 | 3 | ||
4 | const tsConfig = require('../../tsconfig.json') | 4 | const tsConfig = require('../../tsconfig.json') |
5 | 5 | ||
diff --git a/server/helpers/signup.ts b/server/helpers/signup.ts index 7c73f7c5c..d34ff2db5 100644 --- a/server/helpers/signup.ts +++ b/server/helpers/signup.ts | |||
@@ -21,7 +21,7 @@ async function isSignupAllowed (): Promise<{ allowed: boolean, errorMessage?: st | |||
21 | 21 | ||
22 | function isSignupAllowedForCurrentIP (ip: string) { | 22 | function isSignupAllowedForCurrentIP (ip: string) { |
23 | const addr = ipaddr.parse(ip) | 23 | const addr = ipaddr.parse(ip) |
24 | let excludeList = [ 'blacklist' ] | 24 | const excludeList = [ 'blacklist' ] |
25 | let matched = '' | 25 | let matched = '' |
26 | 26 | ||
27 | // if there is a valid, non-empty whitelist, we exclude all unknown adresses too | 27 | // if there is a valid, non-empty whitelist, we exclude all unknown adresses too |
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 4c6f200f8..ad3b7949e 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { ResultList } from '../../shared' | 1 | import { ResultList } from '../../shared' |
2 | import { ApplicationModel } from '../models/application/application' | 2 | import { execPromise, execPromise2, randomBytesPromise, sha256 } from './core-utils' |
3 | import { execPromise, execPromise2, pseudoRandomBytesPromise, sha256 } from './core-utils' | ||
4 | import { logger } from './logger' | 3 | import { logger } from './logger' |
5 | import { join } from 'path' | 4 | import { join } from 'path' |
6 | import { Instance as ParseTorrent } from 'parse-torrent' | 5 | import { Instance as ParseTorrent } from 'parse-torrent' |
7 | import { remove } from 'fs-extra' | 6 | import { remove } from 'fs-extra' |
8 | import * as memoizee from 'memoizee' | ||
9 | import { CONFIG } from '../initializers/config' | 7 | import { CONFIG } from '../initializers/config' |
8 | import { isVideoFileExtnameValid } from './custom-validators/videos' | ||
10 | 9 | ||
11 | function deleteFileAsync (path: string) { | 10 | function deleteFileAsync (path: string) { |
12 | remove(path) | 11 | remove(path) |
@@ -14,7 +13,7 @@ function deleteFileAsync (path: string) { | |||
14 | } | 13 | } |
15 | 14 | ||
16 | async function generateRandomString (size: number) { | 15 | async function generateRandomString (size: number) { |
17 | const raw = await pseudoRandomBytesPromise(size) | 16 | const raw = await randomBytesPromise(size) |
18 | 17 | ||
19 | return raw.toString('hex') | 18 | return raw.toString('hex') |
20 | } | 19 | } |
@@ -32,21 +31,18 @@ function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: | |||
32 | } as ResultList<V> | 31 | } as ResultList<V> |
33 | } | 32 | } |
34 | 33 | ||
35 | const getServerActor = memoizee(async function () { | 34 | function generateVideoImportTmpPath (target: string | ParseTorrent, extensionArg?: string) { |
36 | const application = await ApplicationModel.load() | 35 | const id = typeof target === 'string' |
37 | if (!application) throw Error('Could not load Application from database.') | 36 | ? target |
37 | : target.infoHash | ||
38 | 38 | ||
39 | const actor = application.Account.Actor | 39 | let extension = '.mp4' |
40 | actor.Account = application.Account | 40 | if (extensionArg && isVideoFileExtnameValid(extensionArg)) { |
41 | 41 | extension = extensionArg | |
42 | return actor | 42 | } |
43 | }, { promise: true }) | ||
44 | |||
45 | function generateVideoImportTmpPath (target: string | ParseTorrent) { | ||
46 | const id = typeof target === 'string' ? target : target.infoHash | ||
47 | 43 | ||
48 | const hash = sha256(id) | 44 | const hash = sha256(id) |
49 | return join(CONFIG.STORAGE.TMP_DIR, hash + '-import.mp4') | 45 | return join(CONFIG.STORAGE.TMP_DIR, `${hash}-import${extension}`) |
50 | } | 46 | } |
51 | 47 | ||
52 | function getSecureTorrentName (originalName: string) { | 48 | function getSecureTorrentName (originalName: string) { |
@@ -97,7 +93,6 @@ export { | |||
97 | generateRandomString, | 93 | generateRandomString, |
98 | getFormattedObjects, | 94 | getFormattedObjects, |
99 | getSecureTorrentName, | 95 | getSecureTorrentName, |
100 | getServerActor, | ||
101 | getServerCommit, | 96 | getServerCommit, |
102 | generateVideoImportTmpPath, | 97 | generateVideoImportTmpPath, |
103 | getUUIDFromFilename | 98 | getUUIDFromFilename |
diff --git a/server/helpers/video.ts b/server/helpers/video.ts index 5b9c026b1..6f76cbdfc 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts | |||
@@ -1,17 +1,26 @@ | |||
1 | import { VideoModel } from '../models/video/video' | 1 | import { VideoModel } from '../models/video/video' |
2 | import * as Bluebird from 'bluebird' | 2 | import * as Bluebird from 'bluebird' |
3 | import { | 3 | import { |
4 | isStreamingPlaylist, | ||
5 | MStreamingPlaylistVideo, | ||
6 | MVideo, | ||
4 | MVideoAccountLightBlacklistAllFiles, | 7 | MVideoAccountLightBlacklistAllFiles, |
8 | MVideoFile, | ||
5 | MVideoFullLight, | 9 | MVideoFullLight, |
6 | MVideoIdThumbnail, | 10 | MVideoIdThumbnail, |
11 | MVideoImmutable, | ||
7 | MVideoThumbnail, | 12 | MVideoThumbnail, |
8 | MVideoWithRights | 13 | MVideoWithRights |
9 | } from '@server/typings/models' | 14 | } from '@server/typings/models' |
10 | import { Response } from 'express' | 15 | import { Response } from 'express' |
16 | import { DEFAULT_AUDIO_RESOLUTION } from '@server/initializers/constants' | ||
17 | import { JobQueue } from '@server/lib/job-queue' | ||
18 | import { VideoTranscodingPayload } from '@shared/models' | ||
11 | 19 | ||
12 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 20 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 'only-immutable-attributes' |
13 | 21 | ||
14 | function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight> | 22 | function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight> |
23 | function fetchVideo (id: number | string, fetchType: 'only-immutable-attributes'): Bluebird<MVideoImmutable> | ||
15 | function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail> | 24 | function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail> |
16 | function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights> | 25 | function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights> |
17 | function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail> | 26 | function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail> |
@@ -19,14 +28,16 @@ function fetchVideo ( | |||
19 | id: number | string, | 28 | id: number | string, |
20 | fetchType: VideoFetchType, | 29 | fetchType: VideoFetchType, |
21 | userId?: number | 30 | userId?: number |
22 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> | 31 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable> |
23 | function fetchVideo ( | 32 | function fetchVideo ( |
24 | id: number | string, | 33 | id: number | string, |
25 | fetchType: VideoFetchType, | 34 | fetchType: VideoFetchType, |
26 | userId?: number | 35 | userId?: number |
27 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> { | 36 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail | MVideoImmutable> { |
28 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) | 37 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) |
29 | 38 | ||
39 | if (fetchType === 'only-immutable-attributes') return VideoModel.loadImmutableAttributes(id) | ||
40 | |||
30 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) | 41 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) |
31 | 42 | ||
32 | if (fetchType === 'only-video') return VideoModel.load(id) | 43 | if (fetchType === 'only-video') return VideoModel.load(id) |
@@ -34,14 +45,23 @@ function fetchVideo ( | |||
34 | if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) | 45 | if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) |
35 | } | 46 | } |
36 | 47 | ||
37 | type VideoFetchByUrlType = 'all' | 'only-video' | 48 | type VideoFetchByUrlType = 'all' | 'only-video' | 'only-immutable-attributes' |
38 | 49 | ||
39 | function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles> | 50 | function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles> |
51 | function fetchVideoByUrl (url: string, fetchType: 'only-immutable-attributes'): Bluebird<MVideoImmutable> | ||
40 | function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail> | 52 | function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail> |
41 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> | 53 | function fetchVideoByUrl ( |
42 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> { | 54 | url: string, |
55 | fetchType: VideoFetchByUrlType | ||
56 | ): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> | ||
57 | function fetchVideoByUrl ( | ||
58 | url: string, | ||
59 | fetchType: VideoFetchByUrlType | ||
60 | ): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> { | ||
43 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) | 61 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) |
44 | 62 | ||
63 | if (fetchType === 'only-immutable-attributes') return VideoModel.loadByUrlImmutableAttributes(url) | ||
64 | |||
45 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) | 65 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) |
46 | } | 66 | } |
47 | 67 | ||
@@ -49,10 +69,39 @@ function getVideoWithAttributes (res: Response) { | |||
49 | return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights | 69 | return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights |
50 | } | 70 | } |
51 | 71 | ||
72 | function addOptimizeOrMergeAudioJob (video: MVideo, videoFile: MVideoFile) { | ||
73 | let dataInput: VideoTranscodingPayload | ||
74 | |||
75 | if (videoFile.isAudio()) { | ||
76 | dataInput = { | ||
77 | type: 'merge-audio' as 'merge-audio', | ||
78 | resolution: DEFAULT_AUDIO_RESOLUTION, | ||
79 | videoUUID: video.uuid, | ||
80 | isNewVideo: true | ||
81 | } | ||
82 | } else { | ||
83 | dataInput = { | ||
84 | type: 'optimize' as 'optimize', | ||
85 | videoUUID: video.uuid, | ||
86 | isNewVideo: true | ||
87 | } | ||
88 | } | ||
89 | |||
90 | return JobQueue.Instance.createJobWithPromise({ type: 'video-transcoding', payload: dataInput }) | ||
91 | } | ||
92 | |||
93 | function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) { | ||
94 | return isStreamingPlaylist(videoOrPlaylist) | ||
95 | ? videoOrPlaylist.Video | ||
96 | : videoOrPlaylist | ||
97 | } | ||
98 | |||
52 | export { | 99 | export { |
53 | VideoFetchType, | 100 | VideoFetchType, |
54 | VideoFetchByUrlType, | 101 | VideoFetchByUrlType, |
55 | fetchVideo, | 102 | fetchVideo, |
56 | getVideoWithAttributes, | 103 | getVideoWithAttributes, |
57 | fetchVideoByUrl | 104 | fetchVideoByUrl, |
105 | addOptimizeOrMergeAudioJob, | ||
106 | extractVideo | ||
58 | } | 107 | } |
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts index 3a99518c6..7cd76d708 100644 --- a/server/helpers/webtorrent.ts +++ b/server/helpers/webtorrent.ts | |||
@@ -9,12 +9,12 @@ import { promisify2 } from './core-utils' | |||
9 | import { MVideo } from '@server/typings/models/video/video' | 9 | import { MVideo } from '@server/typings/models/video/video' |
10 | import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/typings/models/video/video-file' | 10 | import { MVideoFile, MVideoFileRedundanciesOpt } from '@server/typings/models/video/video-file' |
11 | import { isStreamingPlaylist, MStreamingPlaylistVideo } from '@server/typings/models/video/video-streaming-playlist' | 11 | import { isStreamingPlaylist, MStreamingPlaylistVideo } from '@server/typings/models/video/video-streaming-playlist' |
12 | import { STATIC_PATHS, WEBSERVER } from '@server/initializers/constants' | 12 | import { WEBSERVER } from '@server/initializers/constants' |
13 | import * as parseTorrent from 'parse-torrent' | 13 | import * as parseTorrent from 'parse-torrent' |
14 | import * as magnetUtil from 'magnet-uri' | 14 | import * as magnetUtil from 'magnet-uri' |
15 | import { isArray } from '@server/helpers/custom-validators/misc' | 15 | import { isArray } from '@server/helpers/custom-validators/misc' |
16 | import { extractVideo } from '@server/lib/videos' | 16 | import { getTorrentFileName, getVideoFilePath } from '@server/lib/video-paths' |
17 | import { getTorrentFileName, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 17 | import { extractVideo } from '@server/helpers/video' |
18 | 18 | ||
19 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) | 19 | const createTorrentPromise = promisify2<string, any, any>(createTorrent) |
20 | 20 | ||
@@ -39,7 +39,7 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName | |||
39 | if (torrent.files.length !== 1) { | 39 | if (torrent.files.length !== 1) { |
40 | if (timer) clearTimeout(timer) | 40 | if (timer) clearTimeout(timer) |
41 | 41 | ||
42 | for (let file of torrent.files) { | 42 | for (const file of torrent.files) { |
43 | deleteDownloadedFile({ directoryPath, filepath: file.path }) | 43 | deleteDownloadedFile({ directoryPath, filepath: file.path }) |
44 | } | 44 | } |
45 | 45 | ||
@@ -47,15 +47,16 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName | |||
47 | .then(() => rej(new Error('Cannot import torrent ' + torrentId + ': there are multiple files in it'))) | 47 | .then(() => rej(new Error('Cannot import torrent ' + torrentId + ': there are multiple files in it'))) |
48 | } | 48 | } |
49 | 49 | ||
50 | file = torrent.files[ 0 ] | 50 | file = torrent.files[0] |
51 | 51 | ||
52 | // FIXME: avoid creating another stream when https://github.com/webtorrent/webtorrent/issues/1517 is fixed | 52 | // FIXME: avoid creating another stream when https://github.com/webtorrent/webtorrent/issues/1517 is fixed |
53 | const writeStream = createWriteStream(path) | 53 | const writeStream = createWriteStream(path) |
54 | writeStream.on('finish', () => { | 54 | writeStream.on('finish', () => { |
55 | if (timer) clearTimeout(timer) | 55 | if (timer) clearTimeout(timer) |
56 | 56 | ||
57 | return safeWebtorrentDestroy(webtorrent, torrentId, { directoryPath, filepath: file.path }, target.torrentName) | 57 | safeWebtorrentDestroy(webtorrent, torrentId, { directoryPath, filepath: file.path }, target.torrentName) |
58 | .then(() => res(path)) | 58 | .then(() => res(path)) |
59 | .catch(err => logger.error('Cannot destroy webtorrent.', { err })) | ||
59 | }) | 60 | }) |
60 | 61 | ||
61 | file.createReadStream().pipe(writeStream) | 62 | file.createReadStream().pipe(writeStream) |
@@ -63,9 +64,16 @@ async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName | |||
63 | 64 | ||
64 | torrent.on('error', err => rej(err)) | 65 | torrent.on('error', err => rej(err)) |
65 | 66 | ||
66 | timer = setTimeout(async () => { | 67 | timer = setTimeout(() => { |
67 | return safeWebtorrentDestroy(webtorrent, torrentId, file ? { directoryPath, filepath: file.path } : undefined, target.torrentName) | 68 | const err = new Error('Webtorrent download timeout.') |
68 | .then(() => rej(new Error('Webtorrent download timeout.'))) | 69 | |
70 | safeWebtorrentDestroy(webtorrent, torrentId, file ? { directoryPath, filepath: file.path } : undefined, target.torrentName) | ||
71 | .then(() => rej(err)) | ||
72 | .catch(destroyErr => { | ||
73 | logger.error('Cannot destroy webtorrent.', { err: destroyErr }) | ||
74 | rej(err) | ||
75 | }) | ||
76 | |||
69 | }, timeout) | 77 | }, timeout) |
70 | }) | 78 | }) |
71 | } | 79 | } |
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts index 577a59dbf..f0944b94f 100644 --- a/server/helpers/youtube-dl.ts +++ b/server/helpers/youtube-dl.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES } from '../initializers/constants' | 1 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../initializers/constants' |
2 | import { logger } from './logger' | 2 | import { logger } from './logger' |
3 | import { generateVideoImportTmpPath } from './utils' | 3 | import { generateVideoImportTmpPath } from './utils' |
4 | import { join } from 'path' | 4 | import { join } from 'path' |
@@ -12,40 +12,85 @@ export type YoutubeDLInfo = { | |||
12 | name?: string | 12 | name?: string |
13 | description?: string | 13 | description?: string |
14 | category?: number | 14 | category?: number |
15 | language?: string | ||
15 | licence?: number | 16 | licence?: number |
16 | nsfw?: boolean | 17 | nsfw?: boolean |
17 | tags?: string[] | 18 | tags?: string[] |
18 | thumbnailUrl?: string | 19 | thumbnailUrl?: string |
20 | fileExt?: string | ||
19 | originallyPublishedAt?: Date | 21 | originallyPublishedAt?: Date |
20 | } | 22 | } |
21 | 23 | ||
24 | export type YoutubeDLSubs = { | ||
25 | language: string | ||
26 | filename: string | ||
27 | path: string | ||
28 | }[] | ||
29 | |||
22 | const processOptions = { | 30 | const processOptions = { |
23 | maxBuffer: 1024 * 1024 * 10 // 10MB | 31 | maxBuffer: 1024 * 1024 * 10 // 10MB |
24 | } | 32 | } |
25 | 33 | ||
26 | function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> { | 34 | function getYoutubeDLInfo (url: string, opts?: string[]): Promise<YoutubeDLInfo> { |
27 | return new Promise<YoutubeDLInfo>(async (res, rej) => { | 35 | return new Promise<YoutubeDLInfo>((res, rej) => { |
28 | let args = opts || [ '-j', '--flat-playlist' ] | 36 | let args = opts || [ '-j', '--flat-playlist' ] |
29 | args = wrapWithProxyOptions(args) | 37 | args = wrapWithProxyOptions(args) |
30 | 38 | ||
31 | const youtubeDL = await safeGetYoutubeDL() | 39 | safeGetYoutubeDL() |
32 | youtubeDL.getInfo(url, args, processOptions, (err, info) => { | 40 | .then(youtubeDL => { |
33 | if (err) return rej(err) | 41 | youtubeDL.getInfo(url, args, processOptions, (err, info) => { |
34 | if (info.is_live === true) return rej(new Error('Cannot download a live streaming.')) | 42 | if (err) return rej(err) |
43 | if (info.is_live === true) return rej(new Error('Cannot download a live streaming.')) | ||
35 | 44 | ||
36 | const obj = buildVideoInfo(normalizeObject(info)) | 45 | const obj = buildVideoInfo(normalizeObject(info)) |
37 | if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video' | 46 | if (obj.name && obj.name.length < CONSTRAINTS_FIELDS.VIDEOS.NAME.min) obj.name += ' video' |
38 | 47 | ||
39 | return res(obj) | 48 | return res(obj) |
40 | }) | 49 | }) |
50 | }) | ||
51 | .catch(err => rej(err)) | ||
52 | }) | ||
53 | } | ||
54 | |||
55 | function getYoutubeDLSubs (url: string, opts?: object): Promise<YoutubeDLSubs> { | ||
56 | return new Promise<YoutubeDLSubs>((res, rej) => { | ||
57 | const cwd = CONFIG.STORAGE.TMP_DIR | ||
58 | const options = opts || { all: true, format: 'vtt', cwd } | ||
59 | |||
60 | safeGetYoutubeDL() | ||
61 | .then(youtubeDL => { | ||
62 | youtubeDL.getSubs(url, options, (err, files) => { | ||
63 | if (err) return rej(err) | ||
64 | |||
65 | logger.debug('Get subtitles from youtube dl.', { url, files }) | ||
66 | |||
67 | const subtitles = files.reduce((acc, filename) => { | ||
68 | const matched = filename.match(/\.([a-z]{2})\.(vtt|ttml)/i) | ||
69 | |||
70 | if (matched[1]) { | ||
71 | return [ | ||
72 | ...acc, | ||
73 | { | ||
74 | language: matched[1], | ||
75 | path: join(cwd, filename), | ||
76 | filename | ||
77 | } | ||
78 | ] | ||
79 | } | ||
80 | }, []) | ||
81 | |||
82 | return res(subtitles) | ||
83 | }) | ||
84 | }) | ||
85 | .catch(err => rej(err)) | ||
41 | }) | 86 | }) |
42 | } | 87 | } |
43 | 88 | ||
44 | function downloadYoutubeDLVideo (url: string, timeout: number) { | 89 | function downloadYoutubeDLVideo (url: string, extension: string, timeout: number) { |
45 | const path = generateVideoImportTmpPath(url) | 90 | const path = generateVideoImportTmpPath(url, extension) |
46 | let timer | 91 | let timer |
47 | 92 | ||
48 | logger.info('Importing youtubeDL video %s', url) | 93 | logger.info('Importing youtubeDL video %s to %s', url, path) |
49 | 94 | ||
50 | let options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] | 95 | let options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] |
51 | options = wrapWithProxyOptions(options) | 96 | options = wrapWithProxyOptions(options) |
@@ -54,26 +99,34 @@ function downloadYoutubeDLVideo (url: string, timeout: number) { | |||
54 | options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ]) | 99 | options = options.concat([ '--ffmpeg-location', process.env.FFMPEG_PATH ]) |
55 | } | 100 | } |
56 | 101 | ||
57 | return new Promise<string>(async (res, rej) => { | 102 | return new Promise<string>((res, rej) => { |
58 | const youtubeDL = await safeGetYoutubeDL() | 103 | safeGetYoutubeDL() |
59 | youtubeDL.exec(url, options, processOptions, err => { | 104 | .then(youtubeDL => { |
60 | clearTimeout(timer) | 105 | youtubeDL.exec(url, options, processOptions, err => { |
106 | clearTimeout(timer) | ||
61 | 107 | ||
62 | if (err) { | 108 | if (err) { |
63 | remove(path) | 109 | remove(path) |
64 | .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err })) | 110 | .catch(err => logger.error('Cannot delete path on YoutubeDL error.', { err })) |
65 | 111 | ||
66 | return rej(err) | 112 | return rej(err) |
67 | } | 113 | } |
68 | 114 | ||
69 | return res(path) | 115 | return res(path) |
70 | }) | 116 | }) |
71 | 117 | ||
72 | timer = setTimeout(async () => { | 118 | timer = setTimeout(() => { |
73 | await remove(path) | 119 | const err = new Error('YoutubeDL download timeout.') |
74 | 120 | ||
75 | return rej(new Error('YoutubeDL download timeout.')) | 121 | remove(path) |
76 | }, timeout) | 122 | .finally(() => rej(err)) |
123 | .catch(err => { | ||
124 | logger.error('Cannot remove %s in youtubeDL timeout.', path, { err }) | ||
125 | return rej(err) | ||
126 | }) | ||
127 | }, timeout) | ||
128 | }) | ||
129 | .catch(err => rej(err)) | ||
77 | }) | 130 | }) |
78 | } | 131 | } |
79 | 132 | ||
@@ -103,7 +156,7 @@ async function updateYoutubeDLBinary () { | |||
103 | 156 | ||
104 | const url = result.headers.location | 157 | const url = result.headers.location |
105 | const downloadFile = request.get(url) | 158 | const downloadFile = request.get(url) |
106 | const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(url)[ 1 ] | 159 | const newVersion = /yt-dl\.org\/downloads\/(\d{4}\.\d\d\.\d\d(\.\d)?)\/youtube-dl/.exec(url)[1] |
107 | 160 | ||
108 | downloadFile.on('response', result => { | 161 | downloadFile.on('response', result => { |
109 | if (result.statusCode !== 200) { | 162 | if (result.statusCode !== 200) { |
@@ -173,6 +226,7 @@ function buildOriginallyPublishedAt (obj: any) { | |||
173 | export { | 226 | export { |
174 | updateYoutubeDLBinary, | 227 | updateYoutubeDLBinary, |
175 | downloadYoutubeDLVideo, | 228 | downloadYoutubeDLVideo, |
229 | getYoutubeDLSubs, | ||
176 | getYoutubeDLInfo, | 230 | getYoutubeDLInfo, |
177 | safeGetYoutubeDL, | 231 | safeGetYoutubeDL, |
178 | buildOriginallyPublishedAt | 232 | buildOriginallyPublishedAt |
@@ -199,16 +253,18 @@ function normalizeObject (obj: any) { | |||
199 | return newObj | 253 | return newObj |
200 | } | 254 | } |
201 | 255 | ||
202 | function buildVideoInfo (obj: any) { | 256 | function buildVideoInfo (obj: any): YoutubeDLInfo { |
203 | return { | 257 | return { |
204 | name: titleTruncation(obj.title), | 258 | name: titleTruncation(obj.title), |
205 | description: descriptionTruncation(obj.description), | 259 | description: descriptionTruncation(obj.description), |
206 | category: getCategory(obj.categories), | 260 | category: getCategory(obj.categories), |
207 | licence: getLicence(obj.license), | 261 | licence: getLicence(obj.license), |
262 | language: getLanguage(obj.language), | ||
208 | nsfw: isNSFW(obj), | 263 | nsfw: isNSFW(obj), |
209 | tags: getTags(obj.tags), | 264 | tags: getTags(obj.tags), |
210 | thumbnailUrl: obj.thumbnail || undefined, | 265 | thumbnailUrl: obj.thumbnail || undefined, |
211 | originallyPublishedAt: buildOriginallyPublishedAt(obj) | 266 | originallyPublishedAt: buildOriginallyPublishedAt(obj), |
267 | fileExt: obj.ext | ||
212 | } | 268 | } |
213 | } | 269 | } |
214 | 270 | ||
@@ -246,7 +302,12 @@ function getTags (tags: any) { | |||
246 | function getLicence (licence: string) { | 302 | function getLicence (licence: string) { |
247 | if (!licence) return undefined | 303 | if (!licence) return undefined |
248 | 304 | ||
249 | if (licence.indexOf('Creative Commons Attribution') !== -1) return 1 | 305 | if (licence.includes('Creative Commons Attribution')) return 1 |
306 | |||
307 | for (const key of Object.keys(VIDEO_LICENCES)) { | ||
308 | const peertubeLicence = VIDEO_LICENCES[key] | ||
309 | if (peertubeLicence.toLowerCase() === licence.toLowerCase()) return parseInt(key, 10) | ||
310 | } | ||
250 | 311 | ||
251 | return undefined | 312 | return undefined |
252 | } | 313 | } |
@@ -267,6 +328,10 @@ function getCategory (categories: string[]) { | |||
267 | return undefined | 328 | return undefined |
268 | } | 329 | } |
269 | 330 | ||
331 | function getLanguage (language: string) { | ||
332 | return VIDEO_LANGUAGES[language] ? language : undefined | ||
333 | } | ||
334 | |||
270 | function wrapWithProxyOptions (options: string[]) { | 335 | function wrapWithProxyOptions (options: string[]) { |
271 | if (CONFIG.IMPORT.VIDEOS.HTTP.PROXY.ENABLED) { | 336 | if (CONFIG.IMPORT.VIDEOS.HTTP.PROXY.ENABLED) { |
272 | logger.debug('Using proxy for YoutubeDL') | 337 | logger.debug('Using proxy for YoutubeDL') |
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts index 44efd346c..f111be2ae 100644 --- a/server/initializers/checker-after-init.ts +++ b/server/initializers/checker-after-init.ts | |||
@@ -1,22 +1,21 @@ | |||
1 | import * as config from 'config' | 1 | import * as config from 'config' |
2 | import { isProdInstance, isTestInstance } from '../helpers/core-utils' | 2 | import { isProdInstance, isTestInstance } from '../helpers/core-utils' |
3 | import { UserModel } from '../models/account/user' | 3 | import { UserModel } from '../models/account/user' |
4 | import { ApplicationModel } from '../models/application/application' | 4 | import { getServerActor, ApplicationModel } from '../models/application/application' |
5 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 5 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
6 | import { parse } from 'url' | 6 | import { URL } from 'url' |
7 | import { CONFIG } from './config' | 7 | import { CONFIG, isEmailEnabled } from './config' |
8 | import { logger } from '../helpers/logger' | 8 | import { logger } from '../helpers/logger' |
9 | import { getServerActor } from '../helpers/utils' | ||
10 | import { RecentlyAddedStrategy } from '../../shared/models/redundancy' | 9 | import { RecentlyAddedStrategy } from '../../shared/models/redundancy' |
11 | import { isArray } from '../helpers/custom-validators/misc' | 10 | import { isArray } from '../helpers/custom-validators/misc' |
12 | import { uniq } from 'lodash' | 11 | import { uniq } from 'lodash' |
13 | import { Emailer } from '../lib/emailer' | ||
14 | import { WEBSERVER } from './constants' | 12 | import { WEBSERVER } from './constants' |
13 | import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type' | ||
15 | 14 | ||
16 | async function checkActivityPubUrls () { | 15 | async function checkActivityPubUrls () { |
17 | const actor = await getServerActor() | 16 | const actor = await getServerActor() |
18 | 17 | ||
19 | const parsed = parse(actor.url) | 18 | const parsed = new URL(actor.url) |
20 | if (WEBSERVER.HOST !== parsed.host) { | 19 | if (WEBSERVER.HOST !== parsed.host) { |
21 | const NODE_ENV = config.util.getEnv('NODE_ENV') | 20 | const NODE_ENV = config.util.getEnv('NODE_ENV') |
22 | const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR') | 21 | const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR') |
@@ -41,7 +40,7 @@ function checkConfig () { | |||
41 | } | 40 | } |
42 | 41 | ||
43 | // Email verification | 42 | // Email verification |
44 | if (!Emailer.isEnabled()) { | 43 | if (!isEmailEnabled()) { |
45 | if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { | 44 | if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { |
46 | return 'Emailer is disabled but you require signup email verification.' | 45 | return 'Emailer is disabled but you require signup email verification.' |
47 | } | 46 | } |
@@ -55,7 +54,7 @@ function checkConfig () { | |||
55 | const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY | 54 | const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY |
56 | { | 55 | { |
57 | const available = [ 'do_not_list', 'blur', 'display' ] | 56 | const available = [ 'do_not_list', 'blur', 'display' ] |
58 | if (available.indexOf(defaultNSFWPolicy) === -1) { | 57 | if (available.includes(defaultNSFWPolicy) === false) { |
59 | return 'NSFW policy setting should be ' + available.join(' or ') + ' instead of ' + defaultNSFWPolicy | 58 | return 'NSFW policy setting should be ' + available.join(' or ') + ' instead of ' + defaultNSFWPolicy |
60 | } | 59 | } |
61 | } | 60 | } |
@@ -65,7 +64,7 @@ function checkConfig () { | |||
65 | if (isArray(redundancyVideos)) { | 64 | if (isArray(redundancyVideos)) { |
66 | const available = [ 'most-views', 'trending', 'recently-added' ] | 65 | const available = [ 'most-views', 'trending', 'recently-added' ] |
67 | for (const r of redundancyVideos) { | 66 | for (const r of redundancyVideos) { |
68 | if (available.indexOf(r.strategy) === -1) { | 67 | if (available.includes(r.strategy) === false) { |
69 | return 'Videos redundancy should have ' + available.join(' or ') + ' strategy instead of ' + r.strategy | 68 | return 'Videos redundancy should have ' + available.join(' or ') + ' strategy instead of ' + r.strategy |
70 | } | 69 | } |
71 | 70 | ||
@@ -88,6 +87,13 @@ function checkConfig () { | |||
88 | return 'Videos redundancy should be an array (you must uncomment lines containing - too)' | 87 | return 'Videos redundancy should be an array (you must uncomment lines containing - too)' |
89 | } | 88 | } |
90 | 89 | ||
90 | // Remote redundancies | ||
91 | const acceptFrom = CONFIG.REMOTE_REDUNDANCY.VIDEOS.ACCEPT_FROM | ||
92 | const acceptFromValues = new Set<VideoRedundancyConfigFilter>([ 'nobody', 'anybody', 'followings' ]) | ||
93 | if (acceptFromValues.has(acceptFrom) === false) { | ||
94 | return 'remote_redundancy.videos.accept_from has an incorrect value' | ||
95 | } | ||
96 | |||
91 | // Check storage directory locations | 97 | // Check storage directory locations |
92 | if (isProdInstance()) { | 98 | if (isProdInstance()) { |
93 | const configStorage = config.get('storage') | 99 | const configStorage = config.get('storage') |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 9731a0af9..56f8156c6 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -22,6 +22,8 @@ function checkMissedConfig () { | |||
22 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', | 22 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', |
23 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', | 23 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', |
24 | 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', 'transcoding.hls.enabled', | 24 | 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', 'transcoding.hls.enabled', |
25 | 'transcoding.resolutions.0p', 'transcoding.resolutions.240p', 'transcoding.resolutions.360p', 'transcoding.resolutions.480p', | ||
26 | 'transcoding.resolutions.720p', 'transcoding.resolutions.1080p', 'transcoding.resolutions.2160p', | ||
25 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'auto_blacklist.videos.of_users.enabled', | 27 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'auto_blacklist.videos.of_users.enabled', |
26 | 'trending.videos.interval_days', | 28 | 'trending.videos.interval_days', |
27 | 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', | 29 | 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', |
@@ -31,12 +33,13 @@ function checkMissedConfig () { | |||
31 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', | 33 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', |
32 | 'history.videos.max_age', 'views.videos.remote.max_age', | 34 | 'history.videos.max_age', 'views.videos.remote.max_age', |
33 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', | 35 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max', |
34 | 'theme.default' | 36 | 'theme.default', |
37 | 'remote_redundancy.videos.accept_from' | ||
35 | ] | 38 | ] |
36 | const requiredAlternatives = [ | 39 | const requiredAlternatives = [ |
37 | [ // set | 40 | [ // set |
38 | ['redis.hostname', 'redis.port'], // alternative | 41 | [ 'redis.hostname', 'redis.port' ], // alternative |
39 | ['redis.socket'] | 42 | [ 'redis.socket' ] |
40 | ] | 43 | ] |
41 | ] | 44 | ] |
42 | const miss: string[] = [] | 45 | const miss: string[] = [] |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 7fd77f3e8..6932b41e1 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -1,10 +1,11 @@ | |||
1 | import { IConfig } from 'config' | 1 | import { IConfig } from 'config' |
2 | import { dirname, join } from 'path' | 2 | import { dirname, join } from 'path' |
3 | import { VideosRedundancy } from '../../shared/models' | 3 | import { VideosRedundancyStrategy } from '../../shared/models' |
4 | // Do not use barrels, remain constants as independent as possible | 4 | // Do not use barrels, remain constants as independent as possible |
5 | import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils' | 5 | import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils' |
6 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | 6 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' |
7 | import * as bytes from 'bytes' | 7 | import * as bytes from 'bytes' |
8 | import { VideoRedundancyConfigFilter } from '@shared/models/redundancy/video-redundancy-config-filter.type' | ||
8 | 9 | ||
9 | // Use a variable to reload the configuration if we need | 10 | // Use a variable to reload the configuration if we need |
10 | let config: IConfig = require('config') | 11 | let config: IConfig = require('config') |
@@ -35,6 +36,8 @@ const CONFIG = { | |||
35 | DB: config.has('redis.db') ? config.get<number>('redis.db') : null | 36 | DB: config.has('redis.db') ? config.get<number>('redis.db') : null |
36 | }, | 37 | }, |
37 | SMTP: { | 38 | SMTP: { |
39 | TRANSPORT: config.has('smtp.transport') ? config.get<string>('smtp.transport') : 'smtp', | ||
40 | SENDMAIL: config.has('smtp.sendmail') ? config.get<string>('smtp.sendmail') : null, | ||
38 | HOSTNAME: config.get<string>('smtp.hostname'), | 41 | HOSTNAME: config.get<string>('smtp.hostname'), |
39 | PORT: config.get<number>('smtp.port'), | 42 | PORT: config.get<number>('smtp.port'), |
40 | USERNAME: config.get<string>('smtp.username'), | 43 | USERNAME: config.get<string>('smtp.username'), |
@@ -117,6 +120,11 @@ const CONFIG = { | |||
117 | STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies')) | 120 | STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies')) |
118 | } | 121 | } |
119 | }, | 122 | }, |
123 | REMOTE_REDUNDANCY: { | ||
124 | VIDEOS: { | ||
125 | ACCEPT_FROM: config.get<VideoRedundancyConfigFilter>('remote_redundancy.videos.accept_from') | ||
126 | } | ||
127 | }, | ||
120 | CSP: { | 128 | CSP: { |
121 | ENABLED: config.get<boolean>('csp.enabled'), | 129 | ENABLED: config.get<boolean>('csp.enabled'), |
122 | REPORT_ONLY: config.get<boolean>('csp.report_only'), | 130 | REPORT_ONLY: config.get<boolean>('csp.report_only'), |
@@ -284,11 +292,16 @@ function registerConfigChangedHandler (fun: Function) { | |||
284 | configChangedHandlers.push(fun) | 292 | configChangedHandlers.push(fun) |
285 | } | 293 | } |
286 | 294 | ||
295 | function isEmailEnabled () { | ||
296 | return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT | ||
297 | } | ||
298 | |||
287 | // --------------------------------------------------------------------------- | 299 | // --------------------------------------------------------------------------- |
288 | 300 | ||
289 | export { | 301 | export { |
290 | CONFIG, | 302 | CONFIG, |
291 | registerConfigChangedHandler | 303 | registerConfigChangedHandler, |
304 | isEmailEnabled | ||
292 | } | 305 | } |
293 | 306 | ||
294 | // --------------------------------------------------------------------------- | 307 | // --------------------------------------------------------------------------- |
@@ -301,10 +314,10 @@ function getLocalConfigFilePath () { | |||
301 | if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}` | 314 | if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}` |
302 | if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}` | 315 | if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}` |
303 | 316 | ||
304 | return join(dirname(configSources[ 0 ].name), filename + '.json') | 317 | return join(dirname(configSources[0].name), filename + '.json') |
305 | } | 318 | } |
306 | 319 | ||
307 | function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { | 320 | function buildVideosRedundancy (objs: any[]): VideosRedundancyStrategy[] { |
308 | if (!objs) return [] | 321 | if (!objs) return [] |
309 | 322 | ||
310 | if (!Array.isArray(objs)) return objs | 323 | if (!Array.isArray(objs)) return objs |
@@ -330,7 +343,7 @@ export function reloadConfig () { | |||
330 | 343 | ||
331 | function purge () { | 344 | function purge () { |
332 | for (const fileName in require.cache) { | 345 | for (const fileName in require.cache) { |
333 | if (-1 === fileName.indexOf(directory())) { | 346 | if (fileName.includes(directory()) === false) { |
334 | continue | 347 | continue |
335 | } | 348 | } |
336 | 349 | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 13e32b6d2..134560717 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -4,7 +4,7 @@ import { ActivityPubActorType } from '../../shared/models/activitypub' | |||
4 | import { FollowState } from '../../shared/models/actors' | 4 | import { FollowState } from '../../shared/models/actors' |
5 | import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' | 5 | import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' |
6 | // Do not use barrels, remain constants as independent as possible | 6 | // Do not use barrels, remain constants as independent as possible |
7 | import { isTestInstance, sanitizeHost, sanitizeUrl, root, parseDurationToMs } from '../helpers/core-utils' | 7 | import { isTestInstance, sanitizeHost, sanitizeUrl, root } from '../helpers/core-utils' |
8 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | 8 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' |
9 | import { invert } from 'lodash' | 9 | import { invert } from 'lodash' |
10 | import { CronRepeatOptions, EveryRepeatOptions } from 'bull' | 10 | import { CronRepeatOptions, EveryRepeatOptions } from 'bull' |
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 470 | 17 | const LAST_MIGRATION_VERSION = 505 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
@@ -59,9 +59,9 @@ const SORTABLE_COLUMNS = { | |||
59 | FOLLOWERS: [ 'createdAt', 'state', 'score' ], | 59 | FOLLOWERS: [ 'createdAt', 'state', 'score' ], |
60 | FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ], | 60 | FOLLOWING: [ 'createdAt', 'redundancyAllowed', 'state' ], |
61 | 61 | ||
62 | VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'trending' ], | 62 | VIDEOS: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'trending' ], |
63 | 63 | ||
64 | VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'views', 'likes', 'match' ], | 64 | VIDEOS_SEARCH: [ 'name', 'duration', 'createdAt', 'publishedAt', 'originallyPublishedAt', 'views', 'likes', 'match' ], |
65 | VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ], | 65 | VIDEO_CHANNELS_SEARCH: [ 'match', 'displayName', 'createdAt' ], |
66 | 66 | ||
67 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], | 67 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], |
@@ -73,7 +73,9 @@ const SORTABLE_COLUMNS = { | |||
73 | 73 | ||
74 | PLUGINS: [ 'name', 'createdAt', 'updatedAt' ], | 74 | PLUGINS: [ 'name', 'createdAt', 'updatedAt' ], |
75 | 75 | ||
76 | AVAILABLE_PLUGINS: [ 'npmName', 'popularity' ] | 76 | AVAILABLE_PLUGINS: [ 'npmName', 'popularity' ], |
77 | |||
78 | VIDEO_REDUNDANCIES: [ 'name' ] | ||
77 | } | 79 | } |
78 | 80 | ||
79 | const OAUTH_LIFETIME = { | 81 | const OAUTH_LIFETIME = { |
@@ -88,9 +90,6 @@ const ROUTE_CACHE_LIFETIME = { | |||
88 | SECURITYTXT: '2 hours', | 90 | SECURITYTXT: '2 hours', |
89 | NODEINFO: '10 minutes', | 91 | NODEINFO: '10 minutes', |
90 | DNT_POLICY: '1 week', | 92 | DNT_POLICY: '1 week', |
91 | OVERVIEWS: { | ||
92 | VIDEOS: '1 hour' | ||
93 | }, | ||
94 | ACTIVITY_PUB: { | 93 | ACTIVITY_PUB: { |
95 | VIDEOS: '1 second' // 1 second, cache concurrent requests after a broadcast for example | 94 | VIDEOS: '1 second' // 1 second, cache concurrent requests after a broadcast for example |
96 | }, | 95 | }, |
@@ -117,45 +116,44 @@ const REMOTE_SCHEME = { | |||
117 | WS: 'wss' | 116 | WS: 'wss' |
118 | } | 117 | } |
119 | 118 | ||
120 | // TODO: remove 'video-file' | 119 | const JOB_ATTEMPTS: { [id in JobType]: number } = { |
121 | const JOB_ATTEMPTS: { [id in (JobType | 'video-file')]: number } = { | ||
122 | 'activitypub-http-broadcast': 5, | 120 | 'activitypub-http-broadcast': 5, |
123 | 'activitypub-http-unicast': 5, | 121 | 'activitypub-http-unicast': 5, |
124 | 'activitypub-http-fetcher': 5, | 122 | 'activitypub-http-fetcher': 5, |
125 | 'activitypub-follow': 5, | 123 | 'activitypub-follow': 5, |
126 | 'video-file-import': 1, | 124 | 'video-file-import': 1, |
127 | 'video-transcoding': 1, | 125 | 'video-transcoding': 1, |
128 | 'video-file': 1, | ||
129 | 'video-import': 1, | 126 | 'video-import': 1, |
130 | 'email': 5, | 127 | 'email': 5, |
131 | 'videos-views': 1, | 128 | 'videos-views': 1, |
132 | 'activitypub-refresher': 1 | 129 | 'activitypub-refresher': 1, |
130 | 'video-redundancy': 1 | ||
133 | } | 131 | } |
134 | const JOB_CONCURRENCY: { [id in (JobType | 'video-file')]: number } = { | 132 | const JOB_CONCURRENCY: { [id in JobType]: number } = { |
135 | 'activitypub-http-broadcast': 1, | 133 | 'activitypub-http-broadcast': 1, |
136 | 'activitypub-http-unicast': 5, | 134 | 'activitypub-http-unicast': 5, |
137 | 'activitypub-http-fetcher': 1, | 135 | 'activitypub-http-fetcher': 1, |
138 | 'activitypub-follow': 1, | 136 | 'activitypub-follow': 1, |
139 | 'video-file-import': 1, | 137 | 'video-file-import': 1, |
140 | 'video-transcoding': 1, | 138 | 'video-transcoding': 1, |
141 | 'video-file': 1, | ||
142 | 'video-import': 1, | 139 | 'video-import': 1, |
143 | 'email': 5, | 140 | 'email': 5, |
144 | 'videos-views': 1, | 141 | 'videos-views': 1, |
145 | 'activitypub-refresher': 1 | 142 | 'activitypub-refresher': 1, |
143 | 'video-redundancy': 1 | ||
146 | } | 144 | } |
147 | const JOB_TTL: { [id in (JobType | 'video-file')]: number } = { | 145 | const JOB_TTL: { [id in JobType]: number } = { |
148 | 'activitypub-http-broadcast': 60000 * 10, // 10 minutes | 146 | 'activitypub-http-broadcast': 60000 * 10, // 10 minutes |
149 | 'activitypub-http-unicast': 60000 * 10, // 10 minutes | 147 | 'activitypub-http-unicast': 60000 * 10, // 10 minutes |
150 | 'activitypub-http-fetcher': 60000 * 10, // 10 minutes | 148 | 'activitypub-http-fetcher': 1000 * 3600 * 10, // 10 hours |
151 | 'activitypub-follow': 60000 * 10, // 10 minutes | 149 | 'activitypub-follow': 60000 * 10, // 10 minutes |
152 | 'video-file-import': 1000 * 3600, // 1 hour | 150 | 'video-file-import': 1000 * 3600, // 1 hour |
153 | 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long | 151 | 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long |
154 | 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long | 152 | 'video-import': 1000 * 3600 * 2, // 2 hours |
155 | 'video-import': 1000 * 3600 * 2, // hours | ||
156 | 'email': 60000 * 10, // 10 minutes | 153 | 'email': 60000 * 10, // 10 minutes |
157 | 'videos-views': undefined, // Unlimited | 154 | 'videos-views': undefined, // Unlimited |
158 | 'activitypub-refresher': 60000 * 10 // 10 minutes | 155 | 'activitypub-refresher': 60000 * 10, // 10 minutes |
156 | 'video-redundancy': 1000 * 3600 * 3 // 3 hours | ||
159 | } | 157 | } |
160 | const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } = { | 158 | const REPEAT_JOBS: { [ id: string ]: EveryRepeatOptions | CronRepeatOptions } = { |
161 | 'videos-views': { | 159 | 'videos-views': { |
@@ -285,7 +283,7 @@ const CONSTRAINTS_FIELDS = { | |||
285 | COUNT: { min: 0 } | 283 | COUNT: { min: 0 } |
286 | }, | 284 | }, |
287 | VIDEO_COMMENTS: { | 285 | VIDEO_COMMENTS: { |
288 | TEXT: { min: 1, max: 3000 }, // Length | 286 | TEXT: { min: 1, max: 10000 }, // Length |
289 | URL: { min: 3, max: 2000 } // Length | 287 | URL: { min: 3, max: 2000 } // Length |
290 | }, | 288 | }, |
291 | VIDEO_SHARE: { | 289 | VIDEO_SHARE: { |
@@ -309,6 +307,8 @@ let CONTACT_FORM_LIFETIME = 60000 * 60 // 1 hour | |||
309 | 307 | ||
310 | const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = { | 308 | const VIDEO_TRANSCODING_FPS: VideoTranscodingFPS = { |
311 | MIN: 10, | 309 | MIN: 10, |
310 | STANDARD: [ 24, 25, 30 ], | ||
311 | HD_STANDARD: [ 50, 60 ], | ||
312 | AVERAGE: 30, | 312 | AVERAGE: 30, |
313 | MAX: 60, | 313 | MAX: 60, |
314 | KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum) | 314 | KEEP_ORIGIN_FPS_RESOLUTION_MIN: 720 // We keep the original FPS on high resolutions (720 minimum) |
@@ -358,42 +358,42 @@ const VIDEO_LICENCES = { | |||
358 | 7: 'Public Domain Dedication' | 358 | 7: 'Public Domain Dedication' |
359 | } | 359 | } |
360 | 360 | ||
361 | let VIDEO_LANGUAGES: { [id: string]: string } = {} | 361 | const VIDEO_LANGUAGES: { [id: string]: string } = {} |
362 | 362 | ||
363 | const VIDEO_PRIVACIES = { | 363 | const VIDEO_PRIVACIES = { |
364 | [ VideoPrivacy.PUBLIC ]: 'Public', | 364 | [VideoPrivacy.PUBLIC]: 'Public', |
365 | [ VideoPrivacy.UNLISTED ]: 'Unlisted', | 365 | [VideoPrivacy.UNLISTED]: 'Unlisted', |
366 | [ VideoPrivacy.PRIVATE ]: 'Private', | 366 | [VideoPrivacy.PRIVATE]: 'Private', |
367 | [ VideoPrivacy.INTERNAL ]: 'Internal' | 367 | [VideoPrivacy.INTERNAL]: 'Internal' |
368 | } | 368 | } |
369 | 369 | ||
370 | const VIDEO_STATES = { | 370 | const VIDEO_STATES = { |
371 | [ VideoState.PUBLISHED ]: 'Published', | 371 | [VideoState.PUBLISHED]: 'Published', |
372 | [ VideoState.TO_TRANSCODE ]: 'To transcode', | 372 | [VideoState.TO_TRANSCODE]: 'To transcode', |
373 | [ VideoState.TO_IMPORT ]: 'To import' | 373 | [VideoState.TO_IMPORT]: 'To import' |
374 | } | 374 | } |
375 | 375 | ||
376 | const VIDEO_IMPORT_STATES = { | 376 | const VIDEO_IMPORT_STATES = { |
377 | [ VideoImportState.FAILED ]: 'Failed', | 377 | [VideoImportState.FAILED]: 'Failed', |
378 | [ VideoImportState.PENDING ]: 'Pending', | 378 | [VideoImportState.PENDING]: 'Pending', |
379 | [ VideoImportState.SUCCESS ]: 'Success' | 379 | [VideoImportState.SUCCESS]: 'Success' |
380 | } | 380 | } |
381 | 381 | ||
382 | const VIDEO_ABUSE_STATES = { | 382 | const VIDEO_ABUSE_STATES = { |
383 | [ VideoAbuseState.PENDING ]: 'Pending', | 383 | [VideoAbuseState.PENDING]: 'Pending', |
384 | [ VideoAbuseState.REJECTED ]: 'Rejected', | 384 | [VideoAbuseState.REJECTED]: 'Rejected', |
385 | [ VideoAbuseState.ACCEPTED ]: 'Accepted' | 385 | [VideoAbuseState.ACCEPTED]: 'Accepted' |
386 | } | 386 | } |
387 | 387 | ||
388 | const VIDEO_PLAYLIST_PRIVACIES = { | 388 | const VIDEO_PLAYLIST_PRIVACIES = { |
389 | [ VideoPlaylistPrivacy.PUBLIC ]: 'Public', | 389 | [VideoPlaylistPrivacy.PUBLIC]: 'Public', |
390 | [ VideoPlaylistPrivacy.UNLISTED ]: 'Unlisted', | 390 | [VideoPlaylistPrivacy.UNLISTED]: 'Unlisted', |
391 | [ VideoPlaylistPrivacy.PRIVATE ]: 'Private' | 391 | [VideoPlaylistPrivacy.PRIVATE]: 'Private' |
392 | } | 392 | } |
393 | 393 | ||
394 | const VIDEO_PLAYLIST_TYPES = { | 394 | const VIDEO_PLAYLIST_TYPES = { |
395 | [ VideoPlaylistType.REGULAR ]: 'Regular', | 395 | [VideoPlaylistType.REGULAR]: 'Regular', |
396 | [ VideoPlaylistType.WATCH_LATER ]: 'Watch later' | 396 | [VideoPlaylistType.WATCH_LATER]: 'Watch later' |
397 | } | 397 | } |
398 | 398 | ||
399 | const MIMETYPES = { | 399 | const MIMETYPES = { |
@@ -419,7 +419,8 @@ const MIMETYPES = { | |||
419 | 'image/png': '.png', | 419 | 'image/png': '.png', |
420 | 'image/jpg': '.jpg', | 420 | 'image/jpg': '.jpg', |
421 | 'image/jpeg': '.jpg' | 421 | 'image/jpeg': '.jpg' |
422 | } | 422 | }, |
423 | EXT_MIMETYPE: null as { [ id: string ]: string } | ||
423 | }, | 424 | }, |
424 | VIDEO_CAPTIONS: { | 425 | VIDEO_CAPTIONS: { |
425 | MIMETYPE_EXT: { | 426 | MIMETYPE_EXT: { |
@@ -435,13 +436,14 @@ const MIMETYPES = { | |||
435 | } | 436 | } |
436 | } | 437 | } |
437 | MIMETYPES.AUDIO.EXT_MIMETYPE = invert(MIMETYPES.AUDIO.MIMETYPE_EXT) | 438 | MIMETYPES.AUDIO.EXT_MIMETYPE = invert(MIMETYPES.AUDIO.MIMETYPE_EXT) |
439 | MIMETYPES.IMAGE.EXT_MIMETYPE = invert(MIMETYPES.IMAGE.MIMETYPE_EXT) | ||
438 | 440 | ||
439 | // --------------------------------------------------------------------------- | 441 | // --------------------------------------------------------------------------- |
440 | 442 | ||
441 | const OVERVIEWS = { | 443 | const OVERVIEWS = { |
442 | VIDEOS: { | 444 | VIDEOS: { |
443 | SAMPLE_THRESHOLD: 6, | 445 | SAMPLE_THRESHOLD: 6, |
444 | SAMPLES_COUNT: 2 | 446 | SAMPLES_COUNT: 20 |
445 | } | 447 | } |
446 | } | 448 | } |
447 | 449 | ||
@@ -462,7 +464,7 @@ const ACTIVITY_PUB = { | |||
462 | ACCEPT_HEADER: 'application/activity+json, application/ld+json', | 464 | ACCEPT_HEADER: 'application/activity+json, application/ld+json', |
463 | PUBLIC: 'https://www.w3.org/ns/activitystreams#Public', | 465 | PUBLIC: 'https://www.w3.org/ns/activitystreams#Public', |
464 | COLLECTION_ITEMS_PER_PAGE: 10, | 466 | COLLECTION_ITEMS_PER_PAGE: 10, |
465 | FETCH_PAGE_LIMIT: 100, | 467 | FETCH_PAGE_LIMIT: 2000, |
466 | URL_MIME_TYPES: { | 468 | URL_MIME_TYPES: { |
467 | VIDEO: [] as string[], | 469 | VIDEO: [] as string[], |
468 | TORRENT: [ 'application/x-bittorrent' ], | 470 | TORRENT: [ 'application/x-bittorrent' ], |
@@ -497,6 +499,7 @@ let PRIVATE_RSA_KEY_SIZE = 2048 | |||
497 | const BCRYPT_SALT_SIZE = 10 | 499 | const BCRYPT_SALT_SIZE = 10 |
498 | 500 | ||
499 | const USER_PASSWORD_RESET_LIFETIME = 60000 * 60 // 60 minutes | 501 | const USER_PASSWORD_RESET_LIFETIME = 60000 * 60 // 60 minutes |
502 | const USER_PASSWORD_CREATE_LIFETIME = 60000 * 60 * 24 * 7 // 7 days | ||
500 | 503 | ||
501 | const USER_EMAIL_VERIFY_LIFETIME = 60000 * 60 // 60 minutes | 504 | const USER_EMAIL_VERIFY_LIFETIME = 60000 * 60 // 60 minutes |
502 | 505 | ||
@@ -533,7 +536,7 @@ const LAZY_STATIC_PATHS = { | |||
533 | } | 536 | } |
534 | 537 | ||
535 | // Cache control | 538 | // Cache control |
536 | let STATIC_MAX_AGE = { | 539 | const STATIC_MAX_AGE = { |
537 | SERVER: '2h', | 540 | SERVER: '2h', |
538 | CLIENT: '30d' | 541 | CLIENT: '30d' |
539 | } | 542 | } |
@@ -541,11 +544,13 @@ let STATIC_MAX_AGE = { | |||
541 | // Videos thumbnail size | 544 | // Videos thumbnail size |
542 | const THUMBNAILS_SIZE = { | 545 | const THUMBNAILS_SIZE = { |
543 | width: 223, | 546 | width: 223, |
544 | height: 122 | 547 | height: 122, |
548 | minWidth: 150 | ||
545 | } | 549 | } |
546 | const PREVIEWS_SIZE = { | 550 | const PREVIEWS_SIZE = { |
547 | width: 850, | 551 | width: 850, |
548 | height: 480 | 552 | height: 480, |
553 | minWidth: 400 | ||
549 | } | 554 | } |
550 | const AVATARS_SIZE = { | 555 | const AVATARS_SIZE = { |
551 | width: 120, | 556 | width: 120, |
@@ -640,6 +645,8 @@ const P2P_MEDIA_LOADER_PEER_VERSION = 2 | |||
640 | const PLUGIN_GLOBAL_CSS_FILE_NAME = 'plugins-global.css' | 645 | const PLUGIN_GLOBAL_CSS_FILE_NAME = 'plugins-global.css' |
641 | const PLUGIN_GLOBAL_CSS_PATH = join(CONFIG.STORAGE.TMP_DIR, PLUGIN_GLOBAL_CSS_FILE_NAME) | 646 | const PLUGIN_GLOBAL_CSS_PATH = join(CONFIG.STORAGE.TMP_DIR, PLUGIN_GLOBAL_CSS_FILE_NAME) |
642 | 647 | ||
648 | let PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME = 1000 * 60 * 5 // 5 minutes | ||
649 | |||
643 | const DEFAULT_THEME_NAME = 'default' | 650 | const DEFAULT_THEME_NAME = 'default' |
644 | const DEFAULT_USER_THEME_NAME = 'instance-default' | 651 | const DEFAULT_USER_THEME_NAME = 'instance-default' |
645 | 652 | ||
@@ -669,18 +676,20 @@ if (isTestInstance() === true) { | |||
669 | SCHEDULER_INTERVALS_MS.removeOldViews = 5000 | 676 | SCHEDULER_INTERVALS_MS.removeOldViews = 5000 |
670 | SCHEDULER_INTERVALS_MS.updateVideos = 5000 | 677 | SCHEDULER_INTERVALS_MS.updateVideos = 5000 |
671 | SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000 | 678 | SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000 |
672 | REPEAT_JOBS[ 'videos-views' ] = { every: 5000 } | 679 | REPEAT_JOBS['videos-views'] = { every: 5000 } |
673 | 680 | ||
674 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 | 681 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 |
675 | 682 | ||
676 | VIDEO_VIEW_LIFETIME = 1000 // 1 second | 683 | VIDEO_VIEW_LIFETIME = 1000 // 1 second |
677 | CONTACT_FORM_LIFETIME = 1000 // 1 second | 684 | CONTACT_FORM_LIFETIME = 1000 // 1 second |
678 | 685 | ||
679 | JOB_ATTEMPTS[ 'email' ] = 1 | 686 | JOB_ATTEMPTS['email'] = 1 |
680 | 687 | ||
681 | FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 | 688 | FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 |
682 | MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1 | 689 | MEMOIZE_TTL.OVERVIEWS_SAMPLE = 3000 |
683 | ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms' | 690 | OVERVIEWS.VIDEOS.SAMPLE_THRESHOLD = 2 |
691 | |||
692 | PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME = 5000 | ||
684 | } | 693 | } |
685 | 694 | ||
686 | updateWebserverUrls() | 695 | updateWebserverUrls() |
@@ -757,6 +766,7 @@ export { | |||
757 | LRU_CACHE, | 766 | LRU_CACHE, |
758 | JOB_REQUEST_TIMEOUT, | 767 | JOB_REQUEST_TIMEOUT, |
759 | USER_PASSWORD_RESET_LIFETIME, | 768 | USER_PASSWORD_RESET_LIFETIME, |
769 | USER_PASSWORD_CREATE_LIFETIME, | ||
760 | MEMOIZE_TTL, | 770 | MEMOIZE_TTL, |
761 | USER_EMAIL_VERIFY_LIFETIME, | 771 | USER_EMAIL_VERIFY_LIFETIME, |
762 | OVERVIEWS, | 772 | OVERVIEWS, |
@@ -772,6 +782,7 @@ export { | |||
772 | VIDEO_VIEW_LIFETIME, | 782 | VIDEO_VIEW_LIFETIME, |
773 | CONTACT_FORM_LIFETIME, | 783 | CONTACT_FORM_LIFETIME, |
774 | VIDEO_PLAYLIST_PRIVACIES, | 784 | VIDEO_PLAYLIST_PRIVACIES, |
785 | PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME, | ||
775 | ASSETS_PATH, | 786 | ASSETS_PATH, |
776 | loadLanguages, | 787 | loadLanguages, |
777 | buildLanguages | 788 | buildLanguages |
@@ -837,42 +848,42 @@ function loadLanguages () { | |||
837 | function buildLanguages () { | 848 | function buildLanguages () { |
838 | const iso639 = require('iso-639-3') | 849 | const iso639 = require('iso-639-3') |
839 | 850 | ||
840 | const languages: { [ id: string ]: string } = {} | 851 | const languages: { [id: string]: string } = {} |
841 | 852 | ||
842 | const additionalLanguages = { | 853 | const additionalLanguages = { |
843 | 'sgn': true, // Sign languages (macro language) | 854 | sgn: true, // Sign languages (macro language) |
844 | 'ase': true, // American sign language | 855 | ase: true, // American sign language |
845 | 'sdl': true, // Arabian sign language | 856 | sdl: true, // Arabian sign language |
846 | 'bfi': true, // British sign language | 857 | bfi: true, // British sign language |
847 | 'bzs': true, // Brazilian sign language | 858 | bzs: true, // Brazilian sign language |
848 | 'csl': true, // Chinese sign language | 859 | csl: true, // Chinese sign language |
849 | 'cse': true, // Czech sign language | 860 | cse: true, // Czech sign language |
850 | 'dsl': true, // Danish sign language | 861 | dsl: true, // Danish sign language |
851 | 'fsl': true, // French sign language | 862 | fsl: true, // French sign language |
852 | 'gsg': true, // German sign language | 863 | gsg: true, // German sign language |
853 | 'pks': true, // Pakistan sign language | 864 | pks: true, // Pakistan sign language |
854 | 'jsl': true, // Japanese sign language | 865 | jsl: true, // Japanese sign language |
855 | 'sfs': true, // South African sign language | 866 | sfs: true, // South African sign language |
856 | 'swl': true, // Swedish sign language | 867 | swl: true, // Swedish sign language |
857 | 'rsl': true, // Russian sign language: true | 868 | rsl: true, // Russian sign language: true |
858 | 869 | ||
859 | 'epo': true, // Esperanto | 870 | epo: true, // Esperanto |
860 | 'tlh': true, // Klingon | 871 | tlh: true, // Klingon |
861 | 'jbo': true, // Lojban | 872 | jbo: true, // Lojban |
862 | 'avk': true // Kotava | 873 | avk: true // Kotava |
863 | } | 874 | } |
864 | 875 | ||
865 | // Only add ISO639-1 languages and some sign languages (ISO639-3) | 876 | // Only add ISO639-1 languages and some sign languages (ISO639-3) |
866 | iso639 | 877 | iso639 |
867 | .filter(l => { | 878 | .filter(l => { |
868 | return (l.iso6391 !== null && l.type === 'living') || | 879 | return (l.iso6391 !== undefined && l.type === 'living') || |
869 | additionalLanguages[ l.iso6393 ] === true | 880 | additionalLanguages[l.iso6393] === true |
870 | }) | 881 | }) |
871 | .forEach(l => languages[ l.iso6391 || l.iso6393 ] = l.name) | 882 | .forEach(l => { languages[l.iso6391 || l.iso6393] = l.name }) |
872 | 883 | ||
873 | // Override Occitan label | 884 | // Override Occitan label |
874 | languages[ 'oc' ] = 'Occitan' | 885 | languages['oc'] = 'Occitan' |
875 | languages[ 'el' ] = 'Greek' | 886 | languages['el'] = 'Greek' |
876 | 887 | ||
877 | return languages | 888 | return languages |
878 | } | 889 | } |
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 9ec146ab1..eedaf3c4e 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -119,8 +119,6 @@ async function initDatabaseModels (silent: boolean) { | |||
119 | await createFunctions() | 119 | await createFunctions() |
120 | 120 | ||
121 | if (!silent) logger.info('Database %s is ready.', dbname) | 121 | if (!silent) logger.info('Database %s is ready.', dbname) |
122 | |||
123 | return | ||
124 | } | 122 | } |
125 | 123 | ||
126 | // --------------------------------------------------------------------------- | 124 | // --------------------------------------------------------------------------- |
diff --git a/server/initializers/index.ts b/server/initializers/index.ts deleted file mode 100644 index 0fc1a7363..000000000 --- a/server/initializers/index.ts +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | export * from './database' | ||
2 | export * from './installer' | ||
3 | export * from './migrator' | ||
diff --git a/server/initializers/migrations/0005-email-pod.ts b/server/initializers/migrations/0005-email-pod.ts index c34a12255..417c33b1f 100644 --- a/server/initializers/migrations/0005-email-pod.ts +++ b/server/initializers/migrations/0005-email-pod.ts | |||
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird' | |||
3 | import { Migration } from '../../models/migrations' | 3 | import { Migration } from '../../models/migrations' |
4 | 4 | ||
5 | function up (utils: { | 5 | function up (utils: { |
6 | transaction: Sequelize.Transaction, | 6 | transaction: Sequelize.Transaction |
7 | queryInterface: Sequelize.QueryInterface, | 7 | queryInterface: Sequelize.QueryInterface |
8 | sequelize: Sequelize.Sequelize | 8 | sequelize: Sequelize.Sequelize |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | const q = utils.queryInterface | 10 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0010-email-user.ts b/server/initializers/migrations/0010-email-user.ts index 37a7b0bb3..f7d01f6d6 100644 --- a/server/initializers/migrations/0010-email-user.ts +++ b/server/initializers/migrations/0010-email-user.ts | |||
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird' | |||
3 | import { Migration } from '../../models/migrations' | 3 | import { Migration } from '../../models/migrations' |
4 | 4 | ||
5 | function up (utils: { | 5 | function up (utils: { |
6 | transaction: Sequelize.Transaction, | 6 | transaction: Sequelize.Transaction |
7 | queryInterface: Sequelize.QueryInterface, | 7 | queryInterface: Sequelize.QueryInterface |
8 | sequelize: Sequelize.Sequelize | 8 | sequelize: Sequelize.Sequelize |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | const q = utils.queryInterface | 10 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0015-video-views.ts b/server/initializers/migrations/0015-video-views.ts index 25164ff4d..47dd4069b 100644 --- a/server/initializers/migrations/0015-video-views.ts +++ b/server/initializers/migrations/0015-video-views.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | function up (utils: { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0020-video-likes.ts b/server/initializers/migrations/0020-video-likes.ts index 945be5a98..44333f3b0 100644 --- a/server/initializers/migrations/0020-video-likes.ts +++ b/server/initializers/migrations/0020-video-likes.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | function up (utils: { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0025-video-dislikes.ts b/server/initializers/migrations/0025-video-dislikes.ts index 27144c437..2aa22e2d7 100644 --- a/server/initializers/migrations/0025-video-dislikes.ts +++ b/server/initializers/migrations/0025-video-dislikes.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | function up (utils: { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0030-video-category.ts b/server/initializers/migrations/0030-video-category.ts index f784f820d..00cd2d8cf 100644 --- a/server/initializers/migrations/0030-video-category.ts +++ b/server/initializers/migrations/0030-video-category.ts | |||
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird' | |||
3 | import { Migration } from '../../models/migrations' | 3 | import { Migration } from '../../models/migrations' |
4 | 4 | ||
5 | function up (utils: { | 5 | function up (utils: { |
6 | transaction: Sequelize.Transaction, | 6 | transaction: Sequelize.Transaction |
7 | queryInterface: Sequelize.QueryInterface, | 7 | queryInterface: Sequelize.QueryInterface |
8 | sequelize: Sequelize.Sequelize | 8 | sequelize: Sequelize.Sequelize |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | const q = utils.queryInterface | 10 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0035-video-licence.ts b/server/initializers/migrations/0035-video-licence.ts index 3d0b0bac9..61d666c5e 100644 --- a/server/initializers/migrations/0035-video-licence.ts +++ b/server/initializers/migrations/0035-video-licence.ts | |||
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird' | |||
3 | import { Migration } from '../../models/migrations' | 3 | import { Migration } from '../../models/migrations' |
4 | 4 | ||
5 | function up (utils: { | 5 | function up (utils: { |
6 | transaction: Sequelize.Transaction, | 6 | transaction: Sequelize.Transaction |
7 | queryInterface: Sequelize.QueryInterface, | 7 | queryInterface: Sequelize.QueryInterface |
8 | sequelize: Sequelize.Sequelize | 8 | sequelize: Sequelize.Sequelize |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | const q = utils.queryInterface | 10 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0040-video-nsfw.ts b/server/initializers/migrations/0040-video-nsfw.ts index f7f70d3c4..44aec8a6c 100644 --- a/server/initializers/migrations/0040-video-nsfw.ts +++ b/server/initializers/migrations/0040-video-nsfw.ts | |||
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird' | |||
3 | import { Migration } from '../../models/migrations' | 3 | import { Migration } from '../../models/migrations' |
4 | 4 | ||
5 | function up (utils: { | 5 | function up (utils: { |
6 | transaction: Sequelize.Transaction, | 6 | transaction: Sequelize.Transaction |
7 | queryInterface: Sequelize.QueryInterface, | 7 | queryInterface: Sequelize.QueryInterface |
8 | sequelize: Sequelize.Sequelize | 8 | sequelize: Sequelize.Sequelize |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | const q = utils.queryInterface | 10 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0045-user-display-nsfw.ts b/server/initializers/migrations/0045-user-display-nsfw.ts index aef420f0e..07795bd75 100644 --- a/server/initializers/migrations/0045-user-display-nsfw.ts +++ b/server/initializers/migrations/0045-user-display-nsfw.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | function up (utils: { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0050-video-language.ts b/server/initializers/migrations/0050-video-language.ts index 796fa5f95..6f90abb44 100644 --- a/server/initializers/migrations/0050-video-language.ts +++ b/server/initializers/migrations/0050-video-language.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | function up (utils: { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0055-video-uuid.ts b/server/initializers/migrations/0055-video-uuid.ts index e0f904080..8a58aebb8 100644 --- a/server/initializers/migrations/0055-video-uuid.ts +++ b/server/initializers/migrations/0055-video-uuid.ts | |||
@@ -3,8 +3,8 @@ import * as Promise from 'bluebird' | |||
3 | import { Migration } from '../../models/migrations' | 3 | import { Migration } from '../../models/migrations' |
4 | 4 | ||
5 | function up (utils: { | 5 | function up (utils: { |
6 | transaction: Sequelize.Transaction, | 6 | transaction: Sequelize.Transaction |
7 | queryInterface: Sequelize.QueryInterface, | 7 | queryInterface: Sequelize.QueryInterface |
8 | sequelize: Sequelize.Sequelize | 8 | sequelize: Sequelize.Sequelize |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | const q = utils.queryInterface | 10 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0060-video-file.ts b/server/initializers/migrations/0060-video-file.ts index c362cf71a..00647e60e 100644 --- a/server/initializers/migrations/0060-video-file.ts +++ b/server/initializers/migrations/0060-video-file.ts | |||
@@ -2,9 +2,9 @@ import * as Sequelize from 'sequelize' | |||
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | function up (utils: { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize, | 7 | sequelize: Sequelize.Sequelize |
8 | db: any | 8 | db: any |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | const q = utils.queryInterface | 10 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0065-video-file-size.ts b/server/initializers/migrations/0065-video-file-size.ts index e9ce77e50..0bdc675c2 100644 --- a/server/initializers/migrations/0065-video-file-size.ts +++ b/server/initializers/migrations/0065-video-file-size.ts | |||
@@ -5,9 +5,9 @@ import { VideoModel } from '../../models/video/video' | |||
5 | import { getVideoFilePath } from '@server/lib/video-paths' | 5 | import { getVideoFilePath } from '@server/lib/video-paths' |
6 | 6 | ||
7 | function up (utils: { | 7 | function up (utils: { |
8 | transaction: Sequelize.Transaction, | 8 | transaction: Sequelize.Transaction |
9 | queryInterface: Sequelize.QueryInterface, | 9 | queryInterface: Sequelize.QueryInterface |
10 | sequelize: Sequelize.Sequelize, | 10 | sequelize: Sequelize.Sequelize |
11 | db: any | 11 | db: any |
12 | }): Promise<void> { | 12 | }): Promise<void> { |
13 | return utils.db.Video.listOwnedAndPopulateAuthorAndTags() | 13 | return utils.db.Video.listOwnedAndPopulateAuthorAndTags() |
diff --git a/server/initializers/migrations/0070-user-video-quota.ts b/server/initializers/migrations/0070-user-video-quota.ts index 37683432f..1d073f244 100644 --- a/server/initializers/migrations/0070-user-video-quota.ts +++ b/server/initializers/migrations/0070-user-video-quota.ts | |||
@@ -3,9 +3,9 @@ import * as Promise from 'bluebird' | |||
3 | import { Migration } from '../../models/migrations' | 3 | import { Migration } from '../../models/migrations' |
4 | 4 | ||
5 | function up (utils: { | 5 | function up (utils: { |
6 | transaction: Sequelize.Transaction, | 6 | transaction: Sequelize.Transaction |
7 | queryInterface: Sequelize.QueryInterface, | 7 | queryInterface: Sequelize.QueryInterface |
8 | sequelize: Sequelize.Sequelize, | 8 | sequelize: Sequelize.Sequelize |
9 | db: any | 9 | db: any |
10 | }): Promise<void> { | 10 | }): Promise<void> { |
11 | const q = utils.queryInterface | 11 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0075-video-resolutions.ts b/server/initializers/migrations/0075-video-resolutions.ts index e4f26cb77..f56c1b2c3 100644 --- a/server/initializers/migrations/0075-video-resolutions.ts +++ b/server/initializers/migrations/0075-video-resolutions.ts | |||
@@ -5,9 +5,9 @@ import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' | |||
5 | import { readdir, rename } from 'fs-extra' | 5 | import { readdir, rename } from 'fs-extra' |
6 | 6 | ||
7 | function up (utils: { | 7 | function up (utils: { |
8 | transaction: Sequelize.Transaction, | 8 | transaction: Sequelize.Transaction |
9 | queryInterface: Sequelize.QueryInterface, | 9 | queryInterface: Sequelize.QueryInterface |
10 | sequelize: Sequelize.Sequelize, | 10 | sequelize: Sequelize.Sequelize |
11 | db: any | 11 | db: any |
12 | }): Promise<void> { | 12 | }): Promise<void> { |
13 | const torrentDir = CONFIG.STORAGE.TORRENTS_DIR | 13 | const torrentDir = CONFIG.STORAGE.TORRENTS_DIR |
diff --git a/server/initializers/migrations/0080-video-channels.ts b/server/initializers/migrations/0080-video-channels.ts index 5512bdcf1..883224cb0 100644 --- a/server/initializers/migrations/0080-video-channels.ts +++ b/server/initializers/migrations/0080-video-channels.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as uuidv4 from 'uuid/v4' | 2 | import { v4 as uuidv4 } from 'uuid' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize, | 7 | sequelize: Sequelize.Sequelize |
8 | db: any | 8 | db: any |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | const q = utils.queryInterface | 10 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0085-user-role.ts b/server/initializers/migrations/0085-user-role.ts index de75faec2..ec7428fd5 100644 --- a/server/initializers/migrations/0085-user-role.ts +++ b/server/initializers/migrations/0085-user-role.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0090-videos-description.ts b/server/initializers/migrations/0090-videos-description.ts index 6f98dcade..32e518d75 100644 --- a/server/initializers/migrations/0090-videos-description.ts +++ b/server/initializers/migrations/0090-videos-description.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0095-videos-privacy.ts b/server/initializers/migrations/0095-videos-privacy.ts index 4c2bf91d0..c732d6f6a 100644 --- a/server/initializers/migrations/0095-videos-privacy.ts +++ b/server/initializers/migrations/0095-videos-privacy.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts index 96d44a7ce..05fd37406 100644 --- a/server/initializers/migrations/0100-activitypub.ts +++ b/server/initializers/migrations/0100-activitypub.ts | |||
@@ -7,9 +7,9 @@ import { ApplicationModel } from '../../models/application/application' | |||
7 | import { SERVER_ACTOR_NAME } from '../constants' | 7 | import { SERVER_ACTOR_NAME } from '../constants' |
8 | 8 | ||
9 | async function up (utils: { | 9 | async function up (utils: { |
10 | transaction: Sequelize.Transaction, | 10 | transaction: Sequelize.Transaction |
11 | queryInterface: Sequelize.QueryInterface, | 11 | queryInterface: Sequelize.QueryInterface |
12 | sequelize: Sequelize.Sequelize, | 12 | sequelize: Sequelize.Sequelize |
13 | db: any | 13 | db: any |
14 | }): Promise<void> { | 14 | }): Promise<void> { |
15 | const q = utils.queryInterface | 15 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0105-server-mail.ts b/server/initializers/migrations/0105-server-mail.ts index 4b9600e91..5ee37c418 100644 --- a/server/initializers/migrations/0105-server-mail.ts +++ b/server/initializers/migrations/0105-server-mail.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | await utils.queryInterface.removeColumn('Servers', 'email') | 9 | await utils.queryInterface.removeColumn('Servers', 'email') |
diff --git a/server/initializers/migrations/0110-server-key.ts b/server/initializers/migrations/0110-server-key.ts index 5ff6daf69..354cd7e76 100644 --- a/server/initializers/migrations/0110-server-key.ts +++ b/server/initializers/migrations/0110-server-key.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | await utils.queryInterface.removeColumn('Servers', 'publicKey') | 9 | await utils.queryInterface.removeColumn('Servers', 'publicKey') |
diff --git a/server/initializers/migrations/0115-account-avatar.ts b/server/initializers/migrations/0115-account-avatar.ts index b318e8163..604b6394a 100644 --- a/server/initializers/migrations/0115-account-avatar.ts +++ b/server/initializers/migrations/0115-account-avatar.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | await utils.db.Avatar.sync() | 9 | await utils.db.Avatar.sync() |
diff --git a/server/initializers/migrations/0120-video-null.ts b/server/initializers/migrations/0120-video-null.ts index 6d253f04f..1b407b270 100644 --- a/server/initializers/migrations/0120-video-null.ts +++ b/server/initializers/migrations/0120-video-null.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | 9 | ||
diff --git a/server/initializers/migrations/0125-table-lowercase.ts b/server/initializers/migrations/0125-table-lowercase.ts index 78041ccb0..f75a56791 100644 --- a/server/initializers/migrations/0125-table-lowercase.ts +++ b/server/initializers/migrations/0125-table-lowercase.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | await utils.queryInterface.renameTable('Applications', 'application') | 8 | await utils.queryInterface.renameTable('Applications', 'application') |
diff --git a/server/initializers/migrations/0130-user-autoplay-video.ts b/server/initializers/migrations/0130-user-autoplay-video.ts index 9f6878e39..d57934588 100644 --- a/server/initializers/migrations/0130-user-autoplay-video.ts +++ b/server/initializers/migrations/0130-user-autoplay-video.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import * as Promise from 'bluebird' | 2 | import * as Promise from 'bluebird' |
3 | 3 | ||
4 | function up (utils: { | 4 | function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const q = utils.queryInterface | 9 | const q = utils.queryInterface |
diff --git a/server/initializers/migrations/0135-video-channel-actor.ts b/server/initializers/migrations/0135-video-channel-actor.ts index 5ace0f4d2..3f620dfa3 100644 --- a/server/initializers/migrations/0135-video-channel-actor.ts +++ b/server/initializers/migrations/0135-video-channel-actor.ts | |||
@@ -3,8 +3,8 @@ import { DataType } from 'sequelize-typescript' | |||
3 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' | 3 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' |
4 | 4 | ||
5 | async function up (utils: { | 5 | async function up (utils: { |
6 | transaction: Sequelize.Transaction, | 6 | transaction: Sequelize.Transaction |
7 | queryInterface: Sequelize.QueryInterface, | 7 | queryInterface: Sequelize.QueryInterface |
8 | sequelize: Sequelize.Sequelize | 8 | sequelize: Sequelize.Sequelize |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | // Create actor table | 10 | // Create actor table |
@@ -64,10 +64,10 @@ async function up (utils: { | |||
64 | type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", | 64 | type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", |
65 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" | 65 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" |
66 | ) | 66 | ) |
67 | SELECT | 67 | SELECT |
68 | 'Application', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", | 68 | 'Application', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", |
69 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" | 69 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" |
70 | FROM account | 70 | FROM account |
71 | WHERE "applicationId" IS NOT NULL | 71 | WHERE "applicationId" IS NOT NULL |
72 | ` | 72 | ` |
73 | await utils.sequelize.query(query1) | 73 | await utils.sequelize.query(query1) |
@@ -79,10 +79,10 @@ async function up (utils: { | |||
79 | type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", | 79 | type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", |
80 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" | 80 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" |
81 | ) | 81 | ) |
82 | SELECT | 82 | SELECT |
83 | 'Person', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", | 83 | 'Person', uuid, name, url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", |
84 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" | 84 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" |
85 | FROM account | 85 | FROM account |
86 | WHERE "applicationId" IS NULL | 86 | WHERE "applicationId" IS NULL |
87 | ` | 87 | ` |
88 | await utils.sequelize.query(query2) | 88 | await utils.sequelize.query(query2) |
@@ -108,17 +108,17 @@ async function up (utils: { | |||
108 | } | 108 | } |
109 | 109 | ||
110 | { | 110 | { |
111 | const query = ` | 111 | const query = ` |
112 | INSERT INTO actor | 112 | INSERT INTO actor |
113 | ( | 113 | ( |
114 | type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", | 114 | type, uuid, "preferredUsername", url, "publicKey", "privateKey", "followersCount", "followingCount", "inboxUrl", "outboxUrl", |
115 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" | 115 | "sharedInboxUrl", "followersUrl", "followingUrl", "avatarId", "serverId", "createdAt", "updatedAt" |
116 | ) | 116 | ) |
117 | SELECT | 117 | SELECT |
118 | 'Group', "videoChannel".uuid, "videoChannel".uuid, "videoChannel".url, null, null, 0, 0, "videoChannel".url || '/inbox', | 118 | 'Group', "videoChannel".uuid, "videoChannel".uuid, "videoChannel".url, null, null, 0, 0, "videoChannel".url || '/inbox', |
119 | "videoChannel".url || '/outbox', "videoChannel".url || '/inbox', "videoChannel".url || '/followers', "videoChannel".url || '/following', | 119 | "videoChannel".url || '/outbox', "videoChannel".url || '/inbox', "videoChannel".url || '/followers', "videoChannel".url || '/following', |
120 | null, account."serverId", "videoChannel"."createdAt", "videoChannel"."updatedAt" | 120 | null, account."serverId", "videoChannel"."createdAt", "videoChannel"."updatedAt" |
121 | FROM "videoChannel" | 121 | FROM "videoChannel" |
122 | INNER JOIN "account" on "videoChannel"."accountId" = "account".id | 122 | INNER JOIN "account" on "videoChannel"."accountId" = "account".id |
123 | ` | 123 | ` |
124 | await utils.sequelize.query(query) | 124 | await utils.sequelize.query(query) |
@@ -157,13 +157,13 @@ async function up (utils: { | |||
157 | } | 157 | } |
158 | 158 | ||
159 | { | 159 | { |
160 | const query1 = `UPDATE "actorFollow" | 160 | const query1 = `UPDATE "actorFollow" |
161 | SET "actorId" = | 161 | SET "actorId" = |
162 | (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."actorId")` | 162 | (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."actorId")` |
163 | await utils.sequelize.query(query1) | 163 | await utils.sequelize.query(query1) |
164 | 164 | ||
165 | const query2 = `UPDATE "actorFollow" | 165 | const query2 = `UPDATE "actorFollow" |
166 | SET "targetActorId" = | 166 | SET "targetActorId" = |
167 | (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."targetActorId")` | 167 | (SELECT "account"."actorId" FROM account WHERE "account"."id" = "actorFollow"."targetActorId")` |
168 | 168 | ||
169 | await utils.sequelize.query(query2) | 169 | await utils.sequelize.query(query2) |
@@ -189,8 +189,8 @@ async function up (utils: { | |||
189 | await utils.queryInterface.removeConstraint('videoShare', 'videoShare_accountId_fkey') | 189 | await utils.queryInterface.removeConstraint('videoShare', 'videoShare_accountId_fkey') |
190 | } | 190 | } |
191 | 191 | ||
192 | const query = `UPDATE "videoShare" | 192 | const query = `UPDATE "videoShare" |
193 | SET "actorId" = | 193 | SET "actorId" = |
194 | (SELECT "actorId" FROM account WHERE id = "videoShare"."actorId")` | 194 | (SELECT "actorId" FROM account WHERE id = "videoShare"."actorId")` |
195 | await utils.sequelize.query(query) | 195 | await utils.sequelize.query(query) |
196 | 196 | ||
@@ -240,7 +240,7 @@ async function up (utils: { | |||
240 | { | 240 | { |
241 | const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL' | 241 | const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL' |
242 | const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT } | 242 | const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT } |
243 | const [ res ] = await utils.sequelize.query(query, options) | 243 | const [ res ] = await utils.sequelize.query<any>(query, options) |
244 | 244 | ||
245 | for (const actor of res) { | 245 | for (const actor of res) { |
246 | const { privateKey, publicKey } = await createPrivateAndPublicKeys() | 246 | const { privateKey, publicKey } = await createPrivateAndPublicKeys() |
diff --git a/server/initializers/migrations/0140-actor-url.ts b/server/initializers/migrations/0140-actor-url.ts index 020499391..d790988ad 100644 --- a/server/initializers/migrations/0140-actor-url.ts +++ b/server/initializers/migrations/0140-actor-url.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import { WEBSERVER } from '../constants' | 2 | import { WEBSERVER } from '../constants' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const toReplace = WEBSERVER.HOSTNAME + ':443' | 9 | const toReplace = WEBSERVER.HOSTNAME + ':443' |
diff --git a/server/initializers/migrations/0145-delete-author.ts b/server/initializers/migrations/0145-delete-author.ts index cb23d1cc2..6c9427997 100644 --- a/server/initializers/migrations/0145-delete-author.ts +++ b/server/initializers/migrations/0145-delete-author.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | await utils.queryInterface.dropTable('Authors') | 8 | await utils.queryInterface.dropTable('Authors') |
diff --git a/server/initializers/migrations/0150-avatar-cascade.ts b/server/initializers/migrations/0150-avatar-cascade.ts index 821696717..fb3b25773 100644 --- a/server/initializers/migrations/0150-avatar-cascade.ts +++ b/server/initializers/migrations/0150-avatar-cascade.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | await utils.queryInterface.removeConstraint('actor', 'actor_avatarId_fkey') | 8 | await utils.queryInterface.removeConstraint('actor', 'actor_avatarId_fkey') |
diff --git a/server/initializers/migrations/0155-video-comments-enabled.ts b/server/initializers/migrations/0155-video-comments-enabled.ts index 6949d3a0c..691640b35 100644 --- a/server/initializers/migrations/0155-video-comments-enabled.ts +++ b/server/initializers/migrations/0155-video-comments-enabled.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import { Migration } from '../../models/migrations' | 2 | import { Migration } from '../../models/migrations' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const data = { | 9 | const data = { |
diff --git a/server/initializers/migrations/0160-account-route.ts b/server/initializers/migrations/0160-account-route.ts index cab4c72f1..97469948b 100644 --- a/server/initializers/migrations/0160-account-route.ts +++ b/server/initializers/migrations/0160-account-route.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | { | 8 | { |
diff --git a/server/initializers/migrations/0165-video-route.ts b/server/initializers/migrations/0165-video-route.ts index 56d98bc69..aa7c75128 100644 --- a/server/initializers/migrations/0165-video-route.ts +++ b/server/initializers/migrations/0165-video-route.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | { | 8 | { |
diff --git a/server/initializers/migrations/0170-actor-follow-score.ts b/server/initializers/migrations/0170-actor-follow-score.ts index a12b35da9..901a3c799 100644 --- a/server/initializers/migrations/0170-actor-follow-score.ts +++ b/server/initializers/migrations/0170-actor-follow-score.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import { ACTOR_FOLLOW_SCORE } from '../constants' | 2 | import { ACTOR_FOLLOW_SCORE } from '../constants' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | await utils.queryInterface.removeColumn('server', 'score') | 9 | await utils.queryInterface.removeColumn('server', 'score') |
diff --git a/server/initializers/migrations/0175-actor-follow-counts.ts b/server/initializers/migrations/0175-actor-follow-counts.ts index 4fb524181..d7853f8dc 100644 --- a/server/initializers/migrations/0175-actor-follow-counts.ts +++ b/server/initializers/migrations/0175-actor-follow-counts.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | const query = 'UPDATE "actor" SET ' + | 8 | const query = 'UPDATE "actor" SET ' + |
diff --git a/server/initializers/migrations/0180-job-table-delete.ts b/server/initializers/migrations/0180-job-table-delete.ts index df29145d0..fb48a0c9d 100644 --- a/server/initializers/migrations/0180-job-table-delete.ts +++ b/server/initializers/migrations/0180-job-table-delete.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | await utils.queryInterface.dropTable('job') | 8 | await utils.queryInterface.dropTable('job') |
diff --git a/server/initializers/migrations/0185-video-share-url.ts b/server/initializers/migrations/0185-video-share-url.ts index f7eeb0878..f59931e0f 100644 --- a/server/initializers/migrations/0185-video-share-url.ts +++ b/server/initializers/migrations/0185-video-share-url.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | { | 8 | { |
diff --git a/server/initializers/migrations/0190-video-comment-unique-url.ts b/server/initializers/migrations/0190-video-comment-unique-url.ts index b196c9352..a8769ed41 100644 --- a/server/initializers/migrations/0190-video-comment-unique-url.ts +++ b/server/initializers/migrations/0190-video-comment-unique-url.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | { | 8 | { |
diff --git a/server/initializers/migrations/0195-support.ts b/server/initializers/migrations/0195-support.ts index 3b9eabe79..3f7c75dce 100644 --- a/server/initializers/migrations/0195-support.ts +++ b/server/initializers/migrations/0195-support.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | { | 8 | { |
diff --git a/server/initializers/migrations/0200-video-published-at.ts b/server/initializers/migrations/0200-video-published-at.ts index 1701ea07a..d8c7b42a7 100644 --- a/server/initializers/migrations/0200-video-published-at.ts +++ b/server/initializers/migrations/0200-video-published-at.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | 8 | ||
diff --git a/server/initializers/migrations/0205-user-nsfw-policy.ts b/server/initializers/migrations/0205-user-nsfw-policy.ts index d0f6e8962..9c2786f12 100644 --- a/server/initializers/migrations/0205-user-nsfw-policy.ts +++ b/server/initializers/migrations/0205-user-nsfw-policy.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | 8 | ||
diff --git a/server/initializers/migrations/0210-video-language.ts b/server/initializers/migrations/0210-video-language.ts index ca95c7527..ee4ce9266 100644 --- a/server/initializers/migrations/0210-video-language.ts +++ b/server/initializers/migrations/0210-video-language.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import { CONSTRAINTS_FIELDS } from '../constants' | 2 | import { CONSTRAINTS_FIELDS } from '../constants' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | 9 | ||
diff --git a/server/initializers/migrations/0215-video-support-length.ts b/server/initializers/migrations/0215-video-support-length.ts index ba395050f..26c0ca700 100644 --- a/server/initializers/migrations/0215-video-support-length.ts +++ b/server/initializers/migrations/0215-video-support-length.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | { | 8 | { |
diff --git a/server/initializers/migrations/0255-video-blacklist-reason.ts b/server/initializers/migrations/0255-video-blacklist-reason.ts index 69d6efb9e..7de982f93 100644 --- a/server/initializers/migrations/0255-video-blacklist-reason.ts +++ b/server/initializers/migrations/0255-video-blacklist-reason.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { VideoAbuseState } from '../../../shared/models/videos' | ||
3 | 2 | ||
4 | async function up (utils: { | 3 | async function up (utils: { |
5 | transaction: Sequelize.Transaction | 4 | transaction: Sequelize.Transaction |
diff --git a/server/initializers/migrations/0285-description-support.ts b/server/initializers/migrations/0285-description-support.ts index 85ef4ef39..aab3a938f 100644 --- a/server/initializers/migrations/0285-description-support.ts +++ b/server/initializers/migrations/0285-description-support.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0290-account-video-rate-url.ts b/server/initializers/migrations/0290-account-video-rate-url.ts index bdabf2929..b974b1a81 100644 --- a/server/initializers/migrations/0290-account-video-rate-url.ts +++ b/server/initializers/migrations/0290-account-video-rate-url.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0295-video-file-extname.ts b/server/initializers/migrations/0295-video-file-extname.ts index dbf249f66..e1999b072 100644 --- a/server/initializers/migrations/0295-video-file-extname.ts +++ b/server/initializers/migrations/0295-video-file-extname.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0300-user-videos-history-enabled.ts b/server/initializers/migrations/0300-user-videos-history-enabled.ts index aa5fc21fb..5e35e14ba 100644 --- a/server/initializers/migrations/0300-user-videos-history-enabled.ts +++ b/server/initializers/migrations/0300-user-videos-history-enabled.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0305-fix-unfederated-videos.ts b/server/initializers/migrations/0305-fix-unfederated-videos.ts index be206601f..9c5d56b7b 100644 --- a/server/initializers/migrations/0305-fix-unfederated-videos.ts +++ b/server/initializers/migrations/0305-fix-unfederated-videos.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0310-drop-unused-video-indexes.ts b/server/initializers/migrations/0310-drop-unused-video-indexes.ts index d51f430c0..181858d3d 100644 --- a/server/initializers/migrations/0310-drop-unused-video-indexes.ts +++ b/server/initializers/migrations/0310-drop-unused-video-indexes.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const indexNames = [ | 9 | const indexNames = [ |
diff --git a/server/initializers/migrations/0315-user-notifications.ts b/server/initializers/migrations/0315-user-notifications.ts index 8284c58a0..0e3f4fbef 100644 --- a/server/initializers/migrations/0315-user-notifications.ts +++ b/server/initializers/migrations/0315-user-notifications.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | 8 | ||
diff --git a/server/initializers/migrations/0320-blacklist-unfederate.ts b/server/initializers/migrations/0320-blacklist-unfederate.ts index 6fb7bbb90..41de41c55 100644 --- a/server/initializers/migrations/0320-blacklist-unfederate.ts +++ b/server/initializers/migrations/0320-blacklist-unfederate.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | 8 | ||
diff --git a/server/initializers/migrations/0325-video-abuse-fields.ts b/server/initializers/migrations/0325-video-abuse-fields.ts index fca6d666f..d88724a20 100644 --- a/server/initializers/migrations/0325-video-abuse-fields.ts +++ b/server/initializers/migrations/0325-video-abuse-fields.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | 8 | ||
diff --git a/server/initializers/migrations/0330-video-streaming-playlist.ts b/server/initializers/migrations/0330-video-streaming-playlist.ts index c85a762ab..f75541a7f 100644 --- a/server/initializers/migrations/0330-video-streaming-playlist.ts +++ b/server/initializers/migrations/0330-video-streaming-playlist.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | 8 | ||
diff --git a/server/initializers/migrations/0335-video-downloading-enabled.ts b/server/initializers/migrations/0335-video-downloading-enabled.ts index e79466447..c745f1f02 100644 --- a/server/initializers/migrations/0335-video-downloading-enabled.ts +++ b/server/initializers/migrations/0335-video-downloading-enabled.ts | |||
@@ -2,8 +2,8 @@ import * as Sequelize from 'sequelize' | |||
2 | import { Migration } from '../../models/migrations' | 2 | import { Migration } from '../../models/migrations' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const data = { | 9 | const data = { |
diff --git a/server/initializers/migrations/0340-add-originally-published-at.ts b/server/initializers/migrations/0340-add-originally-published-at.ts index fe4f4a5f9..7cbc14ab5 100644 --- a/server/initializers/migrations/0340-add-originally-published-at.ts +++ b/server/initializers/migrations/0340-add-originally-published-at.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize | 6 | sequelize: Sequelize.Sequelize |
7 | }): Promise<void> { | 7 | }): Promise<void> { |
8 | 8 | ||
diff --git a/server/initializers/migrations/0345-video-playlists.ts b/server/initializers/migrations/0345-video-playlists.ts index de69f5b9e..89a14a6ee 100644 --- a/server/initializers/migrations/0345-video-playlists.ts +++ b/server/initializers/migrations/0345-video-playlists.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { VideoPlaylistPrivacy, VideoPlaylistType } from '../../../shared/models/videos' | 2 | import { VideoPlaylistPrivacy, VideoPlaylistType } from '../../../shared/models/videos' |
3 | import * as uuidv4 from 'uuid/v4' | 3 | import { v4 as uuidv4 } from 'uuid' |
4 | import { WEBSERVER } from '../constants' | 4 | import { WEBSERVER } from '../constants' |
5 | 5 | ||
6 | async function up (utils: { | 6 | async function up (utils: { |
7 | transaction: Sequelize.Transaction, | 7 | transaction: Sequelize.Transaction |
8 | queryInterface: Sequelize.QueryInterface, | 8 | queryInterface: Sequelize.QueryInterface |
9 | sequelize: Sequelize.Sequelize | 9 | sequelize: Sequelize.Sequelize |
10 | }): Promise<void> { | 10 | }): Promise<void> { |
11 | const transaction = utils.transaction | 11 | const transaction = utils.transaction |
diff --git a/server/initializers/migrations/0350-video-blacklist-type.ts b/server/initializers/migrations/0350-video-blacklist-type.ts index 4849020ef..f79ae5ec7 100644 --- a/server/initializers/migrations/0350-video-blacklist-type.ts +++ b/server/initializers/migrations/0350-video-blacklist-type.ts | |||
@@ -2,9 +2,9 @@ import * as Sequelize from 'sequelize' | |||
2 | import { VideoBlacklistType } from '../../../shared/models/videos' | 2 | import { VideoBlacklistType } from '../../../shared/models/videos' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface |
7 | sequelize: Sequelize.Sequelize, | 7 | sequelize: Sequelize.Sequelize |
8 | db: any | 8 | db: any |
9 | }): Promise<void> { | 9 | }): Promise<void> { |
10 | { | 10 | { |
diff --git a/server/initializers/migrations/0355-p2p-peer-version.ts b/server/initializers/migrations/0355-p2p-peer-version.ts index 18f23d9b7..89af28d07 100644 --- a/server/initializers/migrations/0355-p2p-peer-version.ts +++ b/server/initializers/migrations/0355-p2p-peer-version.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | 9 | ||
diff --git a/server/initializers/migrations/0360-notification-instance-follower.ts b/server/initializers/migrations/0360-notification-instance-follower.ts index 05caf8e1d..6f9a01a9c 100644 --- a/server/initializers/migrations/0360-notification-instance-follower.ts +++ b/server/initializers/migrations/0360-notification-instance-follower.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0365-user-admin-flags.ts b/server/initializers/migrations/0365-user-admin-flags.ts index 20553100a..b705387da 100644 --- a/server/initializers/migrations/0365-user-admin-flags.ts +++ b/server/initializers/migrations/0365-user-admin-flags.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0370-thumbnail.ts b/server/initializers/migrations/0370-thumbnail.ts index 384ca1a15..07c25436a 100644 --- a/server/initializers/migrations/0370-thumbnail.ts +++ b/server/initializers/migrations/0370-thumbnail.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0375-account-description.ts b/server/initializers/migrations/0375-account-description.ts index 1258563fd..f9af942e0 100644 --- a/server/initializers/migrations/0375-account-description.ts +++ b/server/initializers/migrations/0375-account-description.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const data = { | 9 | const data = { |
diff --git a/server/initializers/migrations/0380-cleanup-timestamps.ts b/server/initializers/migrations/0380-cleanup-timestamps.ts index 2a9fd6f02..18ecfb997 100644 --- a/server/initializers/migrations/0380-cleanup-timestamps.ts +++ b/server/initializers/migrations/0380-cleanup-timestamps.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | try { | 9 | try { |
diff --git a/server/initializers/migrations/0385-remove-actor-uuid.ts b/server/initializers/migrations/0385-remove-actor-uuid.ts index 032c0562b..eefbc386b 100644 --- a/server/initializers/migrations/0385-remove-actor-uuid.ts +++ b/server/initializers/migrations/0385-remove-actor-uuid.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | await utils.queryInterface.removeColumn('actor', 'uuid') | 9 | await utils.queryInterface.removeColumn('actor', 'uuid') |
diff --git a/server/initializers/migrations/0390-user-pending-email.ts b/server/initializers/migrations/0390-user-pending-email.ts index 5ca871746..9cf0affa5 100644 --- a/server/initializers/migrations/0390-user-pending-email.ts +++ b/server/initializers/migrations/0390-user-pending-email.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const data = { | 9 | const data = { |
diff --git a/server/initializers/migrations/0395-user-video-languages.ts b/server/initializers/migrations/0395-user-video-languages.ts index 278698bf4..9c24fbc9a 100644 --- a/server/initializers/migrations/0395-user-video-languages.ts +++ b/server/initializers/migrations/0395-user-video-languages.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const data = { | 9 | const data = { |
diff --git a/server/initializers/migrations/0400-user-theme.ts b/server/initializers/migrations/0400-user-theme.ts index f74d76115..7addb1bb3 100644 --- a/server/initializers/migrations/0400-user-theme.ts +++ b/server/initializers/migrations/0400-user-theme.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const data = { | 9 | const data = { |
diff --git a/server/initializers/migrations/0405-plugin.ts b/server/initializers/migrations/0405-plugin.ts index c55b81960..5c290b986 100644 --- a/server/initializers/migrations/0405-plugin.ts +++ b/server/initializers/migrations/0405-plugin.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0410-video-playlist-element.ts b/server/initializers/migrations/0410-video-playlist-element.ts index f536632a2..1b4692357 100644 --- a/server/initializers/migrations/0410-video-playlist-element.ts +++ b/server/initializers/migrations/0410-video-playlist-element.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0415-thumbnail-auto-generated.ts b/server/initializers/migrations/0415-thumbnail-auto-generated.ts index f822a4c05..98d563c88 100644 --- a/server/initializers/migrations/0415-thumbnail-auto-generated.ts +++ b/server/initializers/migrations/0415-thumbnail-auto-generated.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0420-avatar-lazy.ts b/server/initializers/migrations/0420-avatar-lazy.ts index 5fc57aac2..5c74819d2 100644 --- a/server/initializers/migrations/0420-avatar-lazy.ts +++ b/server/initializers/migrations/0420-avatar-lazy.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts index 4e5f9e6ab..720b99ccc 100644 --- a/server/initializers/migrations/0425-nullable-actor-fields.ts +++ b/server/initializers/migrations/0425-nullable-actor-fields.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const data = { | 9 | const data = { |
diff --git a/server/initializers/migrations/0430-auto-follow-notification-setting.ts b/server/initializers/migrations/0430-auto-follow-notification-setting.ts index 034bdd46d..1734828a4 100644 --- a/server/initializers/migrations/0430-auto-follow-notification-setting.ts +++ b/server/initializers/migrations/0430-auto-follow-notification-setting.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0435-user-modals.ts b/server/initializers/migrations/0435-user-modals.ts index 5c2aa85b5..737440e9b 100644 --- a/server/initializers/migrations/0435-user-modals.ts +++ b/server/initializers/migrations/0435-user-modals.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0440-user-auto-play-next-video.ts b/server/initializers/migrations/0440-user-auto-play-next-video.ts index f0baafe7b..f3f663f59 100644 --- a/server/initializers/migrations/0440-user-auto-play-next-video.ts +++ b/server/initializers/migrations/0440-user-auto-play-next-video.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0445-shared-inbox-optional.ts b/server/initializers/migrations/0445-shared-inbox-optional.ts index dad2d6569..ade1a2a57 100644 --- a/server/initializers/migrations/0445-shared-inbox-optional.ts +++ b/server/initializers/migrations/0445-shared-inbox-optional.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0450-streaming-playlist-files.ts b/server/initializers/migrations/0450-streaming-playlist-files.ts index 460dac8be..08e2e3989 100644 --- a/server/initializers/migrations/0450-streaming-playlist-files.ts +++ b/server/initializers/migrations/0450-streaming-playlist-files.ts | |||
@@ -1,15 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { join } from 'path' | ||
3 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, WEBSERVER } from '@server/initializers/constants' | ||
4 | import { CONFIG } from '@server/initializers/config' | ||
5 | import { pathExists, stat, writeFile } from 'fs-extra' | ||
6 | import * as parseTorrent from 'parse-torrent' | ||
7 | import { createTorrentPromise } from '@server/helpers/webtorrent' | ||
8 | 2 | ||
9 | async function up (utils: { | 3 | async function up (utils: { |
10 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
11 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
12 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
13 | db: any | 7 | db: any |
14 | }): Promise<void> { | 8 | }): Promise<void> { |
15 | { | 9 | { |
@@ -42,8 +36,8 @@ async function up (utils: { | |||
42 | { | 36 | { |
43 | const query = 'insert into "videoFile" ' + | 37 | const query = 'insert into "videoFile" ' + |
44 | '(resolution, size, "infoHash", "videoId", "createdAt", "updatedAt", fps, extname, "videoStreamingPlaylistId")' + | 38 | '(resolution, size, "infoHash", "videoId", "createdAt", "updatedAt", fps, extname, "videoStreamingPlaylistId")' + |
45 | '(SELECT "videoFile".resolution, "videoFile".size, \'fake\', NULL, "videoFile"."createdAt", "videoFile"."updatedAt", "videoFile"."fps", ' + | 39 | '(SELECT "videoFile".resolution, "videoFile".size, \'fake\', NULL, "videoFile"."createdAt", "videoFile"."updatedAt", ' + |
46 | '"videoFile".extname, "videoStreamingPlaylist".id FROM "videoStreamingPlaylist" ' + | 40 | '"videoFile"."fps", "videoFile".extname, "videoStreamingPlaylist".id FROM "videoStreamingPlaylist" ' + |
47 | 'inner join video ON video.id = "videoStreamingPlaylist"."videoId" inner join "videoFile" ON "videoFile"."videoId" = video.id)' | 41 | 'inner join video ON video.id = "videoStreamingPlaylist"."videoId" inner join "videoFile" ON "videoFile"."videoId" = video.id)' |
48 | 42 | ||
49 | await utils.sequelize.query(query, { transaction: utils.transaction }) | 43 | await utils.sequelize.query(query, { transaction: utils.transaction }) |
diff --git a/server/initializers/migrations/0455-soft-delete-video-comments.ts b/server/initializers/migrations/0455-soft-delete-video-comments.ts index bcfb97b56..00e56015f 100644 --- a/server/initializers/migrations/0455-soft-delete-video-comments.ts +++ b/server/initializers/migrations/0455-soft-delete-video-comments.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0460-user-playlist-autoplay.ts b/server/initializers/migrations/0460-user-playlist-autoplay.ts index 3067ac1a4..d6f5081ab 100644 --- a/server/initializers/migrations/0460-user-playlist-autoplay.ts +++ b/server/initializers/migrations/0460-user-playlist-autoplay.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0465-thumbnail-file-url-length.ts b/server/initializers/migrations/0465-thumbnail-file-url-length.ts index db8c85c29..84a4fa0ba 100644 --- a/server/initializers/migrations/0465-thumbnail-file-url-length.ts +++ b/server/initializers/migrations/0465-thumbnail-file-url-length.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | { | 9 | { |
diff --git a/server/initializers/migrations/0470-cleaup-indexes.ts b/server/initializers/migrations/0470-cleaup-indexes.ts index 53e401c2b..7365c30f8 100644 --- a/server/initializers/migrations/0470-cleaup-indexes.ts +++ b/server/initializers/migrations/0470-cleaup-indexes.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | 2 | ||
3 | async function up (utils: { | 3 | async function up (utils: { |
4 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction |
5 | queryInterface: Sequelize.QueryInterface, | 5 | queryInterface: Sequelize.QueryInterface |
6 | sequelize: Sequelize.Sequelize, | 6 | sequelize: Sequelize.Sequelize |
7 | db: any | 7 | db: any |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | await utils.sequelize.query('DROP INDEX IF EXISTS video_share_account_id;') | 9 | await utils.sequelize.query('DROP INDEX IF EXISTS video_share_account_id;') |
diff --git a/server/initializers/migrations/0475-redundancy-expires-on.ts b/server/initializers/migrations/0475-redundancy-expires-on.ts new file mode 100644 index 000000000..edbddba37 --- /dev/null +++ b/server/initializers/migrations/0475-redundancy-expires-on.ts | |||
@@ -0,0 +1,27 @@ | |||
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.DATE, | ||
12 | allowNull: true, | ||
13 | defaultValue: null | ||
14 | } | ||
15 | |||
16 | await utils.queryInterface.changeColumn('videoRedundancy', 'expiresOn', data) | ||
17 | } | ||
18 | } | ||
19 | |||
20 | function down (options) { | ||
21 | throw new Error('Not implemented.') | ||
22 | } | ||
23 | |||
24 | export { | ||
25 | up, | ||
26 | down | ||
27 | } | ||
diff --git a/server/initializers/migrations/0480-caption-file-url.ts b/server/initializers/migrations/0480-caption-file-url.ts new file mode 100644 index 000000000..1f88206d3 --- /dev/null +++ b/server/initializers/migrations/0480-caption-file-url.ts | |||
@@ -0,0 +1,27 @@ | |||
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.STRING, | ||
12 | allowNull: true, | ||
13 | defaultValue: null | ||
14 | } | ||
15 | |||
16 | await utils.queryInterface.addColumn('videoCaption', 'fileUrl', data) | ||
17 | } | ||
18 | } | ||
19 | |||
20 | function down (options) { | ||
21 | throw new Error('Not implemented.') | ||
22 | } | ||
23 | |||
24 | export { | ||
25 | up, | ||
26 | down | ||
27 | } | ||
diff --git a/server/initializers/migrations/0490-abuse-video.ts b/server/initializers/migrations/0490-abuse-video.ts new file mode 100644 index 000000000..610307aa4 --- /dev/null +++ b/server/initializers/migrations/0490-abuse-video.ts | |||
@@ -0,0 +1,26 @@ | |||
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 | }): Promise<void> { | ||
8 | |||
9 | const deletedVideo = { | ||
10 | type: Sequelize.JSONB, | ||
11 | allowNull: true | ||
12 | } | ||
13 | await utils.queryInterface.addColumn('videoAbuse', 'deletedVideo', deletedVideo) | ||
14 | await utils.sequelize.query(`ALTER TABLE "videoAbuse" ALTER COLUMN "videoId" DROP NOT NULL;`) | ||
15 | await utils.sequelize.query(`ALTER TABLE "videoAbuse" DROP CONSTRAINT IF EXISTS "videoAbuse_videoId_fkey";`) | ||
16 | |||
17 | } | ||
18 | |||
19 | function down (options) { | ||
20 | throw new Error('Not implemented.') | ||
21 | } | ||
22 | |||
23 | export { | ||
24 | up, | ||
25 | down | ||
26 | } | ||
diff --git a/server/initializers/migrations/0495-plugin-auth.ts b/server/initializers/migrations/0495-plugin-auth.ts new file mode 100644 index 000000000..ea636a4ad --- /dev/null +++ b/server/initializers/migrations/0495-plugin-auth.ts | |||
@@ -0,0 +1,42 @@ | |||
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 | }): Promise<void> { | ||
8 | |||
9 | { | ||
10 | const password = { | ||
11 | type: Sequelize.STRING, | ||
12 | allowNull: true | ||
13 | } | ||
14 | await utils.queryInterface.changeColumn('user', 'password', password) | ||
15 | } | ||
16 | |||
17 | { | ||
18 | const pluginAuth = { | ||
19 | type: Sequelize.STRING, | ||
20 | allowNull: true | ||
21 | } | ||
22 | await utils.queryInterface.addColumn('user', 'pluginAuth', pluginAuth) | ||
23 | } | ||
24 | |||
25 | { | ||
26 | const authName = { | ||
27 | type: Sequelize.STRING, | ||
28 | allowNull: true | ||
29 | } | ||
30 | await utils.queryInterface.addColumn('oAuthToken', 'authName', authName) | ||
31 | } | ||
32 | |||
33 | } | ||
34 | |||
35 | function down (options) { | ||
36 | throw new Error('Not implemented.') | ||
37 | } | ||
38 | |||
39 | export { | ||
40 | up, | ||
41 | down | ||
42 | } | ||
diff --git a/server/initializers/migrations/0500-playlist-description-length.ts b/server/initializers/migrations/0500-playlist-description-length.ts new file mode 100644 index 000000000..f47f3d96a --- /dev/null +++ b/server/initializers/migrations/0500-playlist-description-length.ts | |||
@@ -0,0 +1,26 @@ | |||
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 | }): Promise<void> { | ||
8 | |||
9 | { | ||
10 | const description = { | ||
11 | type: Sequelize.STRING(1000), | ||
12 | allowNull: true | ||
13 | } | ||
14 | await utils.queryInterface.changeColumn('videoPlaylist', 'description', description) | ||
15 | } | ||
16 | |||
17 | } | ||
18 | |||
19 | function down (options) { | ||
20 | throw new Error('Not implemented.') | ||
21 | } | ||
22 | |||
23 | export { | ||
24 | up, | ||
25 | down | ||
26 | } | ||
diff --git a/server/initializers/migrations/0505-user-last-login-date.ts b/server/initializers/migrations/0505-user-last-login-date.ts new file mode 100644 index 000000000..29d970802 --- /dev/null +++ b/server/initializers/migrations/0505-user-last-login-date.ts | |||
@@ -0,0 +1,26 @@ | |||
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 | }): Promise<void> { | ||
8 | |||
9 | { | ||
10 | const field = { | ||
11 | type: Sequelize.DATE, | ||
12 | allowNull: true | ||
13 | } | ||
14 | await utils.queryInterface.addColumn('user', 'lastLoginDate', field) | ||
15 | } | ||
16 | |||
17 | } | ||
18 | |||
19 | function down (options) { | ||
20 | throw new Error('Not implemented.') | ||
21 | } | ||
22 | |||
23 | export { | ||
24 | up, | ||
25 | down | ||
26 | } | ||
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts index 1cb0116b7..77203ae24 100644 --- a/server/initializers/migrator.ts +++ b/server/initializers/migrator.ts | |||
@@ -20,7 +20,7 @@ async function migrate () { | |||
20 | } | 20 | } |
21 | 21 | ||
22 | const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options) | 22 | const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options) |
23 | if (rows && rows[0] && rows[0].migrationVersion) { | 23 | if (rows?.[0]?.migrationVersion) { |
24 | actualVersion = rows[0].migrationVersion | 24 | actualVersion = rows[0].migrationVersion |
25 | } | 25 | } |
26 | 26 | ||
@@ -60,7 +60,7 @@ export { | |||
60 | async function getMigrationScripts () { | 60 | async function getMigrationScripts () { |
61 | const files = await readdir(path.join(__dirname, 'migrations')) | 61 | const files = await readdir(path.join(__dirname, 'migrations')) |
62 | const filesToMigrate: { | 62 | const filesToMigrate: { |
63 | version: string, | 63 | version: string |
64 | script: string | 64 | script: string |
65 | }[] = [] | 65 | }[] = [] |
66 | 66 | ||
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 0b21de0ca..c743dcf3f 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { Transaction } from 'sequelize' | 2 | import { Transaction } from 'sequelize' |
3 | import * as url from 'url' | 3 | import { URL } from 'url' |
4 | import * as uuidv4 from 'uuid/v4' | 4 | import { v4 as uuidv4 } from 'uuid' |
5 | import { ActivityPubActor, ActivityPubActorType } from '../../../shared/models/activitypub' | 5 | import { ActivityPubActor, ActivityPubActorType, ActivityPubOrderedCollection } from '../../../shared/models/activitypub' |
6 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' | 6 | import { ActivityPubAttributedTo } from '../../../shared/models/activitypub/objects' |
7 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 7 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
8 | import { sanitizeAndCheckActorObject } from '../../helpers/custom-validators/activitypub/actor' | 8 | import { sanitizeAndCheckActorObject } from '../../helpers/custom-validators/activitypub/actor' |
@@ -19,7 +19,6 @@ import { AvatarModel } from '../../models/avatar/avatar' | |||
19 | import { ServerModel } from '../../models/server/server' | 19 | import { ServerModel } from '../../models/server/server' |
20 | import { VideoChannelModel } from '../../models/video/video-channel' | 20 | import { VideoChannelModel } from '../../models/video/video-channel' |
21 | import { JobQueue } from '../job-queue' | 21 | import { JobQueue } from '../job-queue' |
22 | import { getServerActor } from '../../helpers/utils' | ||
23 | import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' | 22 | import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' |
24 | import { sequelizeTypescript } from '../../initializers/database' | 23 | import { sequelizeTypescript } from '../../initializers/database' |
25 | import { | 24 | import { |
@@ -33,9 +32,10 @@ import { | |||
33 | MActorFull, | 32 | MActorFull, |
34 | MActorFullActor, | 33 | MActorFullActor, |
35 | MActorId, | 34 | MActorId, |
36 | MChannel, | 35 | MChannel |
37 | MChannelAccountDefault | ||
38 | } from '../../typings/models' | 36 | } from '../../typings/models' |
37 | import { extname } from 'path' | ||
38 | import { getServerActor } from '@server/models/application/application' | ||
39 | 39 | ||
40 | // Set account keys, this could be long so process after the account creation and do not block the client | 40 | // Set account keys, this could be long so process after the account creation and do not block the client |
41 | function setAsyncActorKeys <T extends MActor> (actor: T) { | 41 | function setAsyncActorKeys <T extends MActor> (actor: T) { |
@@ -117,17 +117,17 @@ async function getOrCreateActorAndServerAndModel ( | |||
117 | if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor | 117 | if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor |
118 | 118 | ||
119 | const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) | 119 | const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) |
120 | if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.') | 120 | if (!actorRefreshed) throw new Error('Actor ' + actor.url + ' does not exist anymore.') |
121 | 121 | ||
122 | if ((created === true || refreshed === true) && updateCollections === true) { | 122 | if ((created === true || refreshed === true) && updateCollections === true) { |
123 | const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' } | 123 | const payload = { uri: actor.outboxUrl, type: 'activity' as 'activity' } |
124 | await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) | 124 | await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload }) |
125 | } | 125 | } |
126 | 126 | ||
127 | // We created a new account: fetch the playlists | 127 | // We created a new account: fetch the playlists |
128 | if (created === true && actor.Account && accountPlaylistsUrl) { | 128 | if (created === true && actor.Account && accountPlaylistsUrl) { |
129 | const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' } | 129 | const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' } |
130 | await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) | 130 | await JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload }) |
131 | } | 131 | } |
132 | 132 | ||
133 | return actorRefreshed | 133 | return actorRefreshed |
@@ -207,7 +207,7 @@ async function fetchActorTotalItems (url: string) { | |||
207 | } | 207 | } |
208 | 208 | ||
209 | try { | 209 | try { |
210 | const { body } = await doRequest(options) | 210 | const { body } = await doRequest<ActivityPubOrderedCollection<unknown>>(options) |
211 | return body.totalItems ? body.totalItems : 0 | 211 | return body.totalItems ? body.totalItems : 0 |
212 | } catch (err) { | 212 | } catch (err) { |
213 | logger.warn('Cannot fetch remote actor count %s.', url, { err }) | 213 | logger.warn('Cannot fetch remote actor count %s.', url, { err }) |
@@ -215,20 +215,28 @@ async function fetchActorTotalItems (url: string) { | |||
215 | } | 215 | } |
216 | } | 216 | } |
217 | 217 | ||
218 | async function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { | 218 | function getAvatarInfoIfExists (actorJSON: ActivityPubActor) { |
219 | if ( | 219 | const mimetypes = MIMETYPES.IMAGE |
220 | actorJSON.icon && actorJSON.icon.type === 'Image' && MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] !== undefined && | 220 | const icon = actorJSON.icon |
221 | isActivityPubUrlValid(actorJSON.icon.url) | ||
222 | ) { | ||
223 | const extension = MIMETYPES.IMAGE.MIMETYPE_EXT[actorJSON.icon.mediaType] | ||
224 | 221 | ||
225 | return { | 222 | if (!icon || icon.type !== 'Image' || !isActivityPubUrlValid(icon.url)) return undefined |
226 | name: uuidv4() + extension, | 223 | |
227 | fileUrl: actorJSON.icon.url | 224 | let extension: string |
228 | } | 225 | |
226 | if (icon.mediaType) { | ||
227 | extension = mimetypes.MIMETYPE_EXT[icon.mediaType] | ||
228 | } else { | ||
229 | const tmp = extname(icon.url) | ||
230 | |||
231 | if (mimetypes.EXT_MIMETYPE[tmp] !== undefined) extension = tmp | ||
229 | } | 232 | } |
230 | 233 | ||
231 | return undefined | 234 | if (!extension) return undefined |
235 | |||
236 | return { | ||
237 | name: uuidv4() + extension, | ||
238 | fileUrl: icon.url | ||
239 | } | ||
232 | } | 240 | } |
233 | 241 | ||
234 | async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) { | 242 | async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) { |
@@ -271,7 +279,10 @@ async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannel | |||
271 | 279 | ||
272 | if (statusCode === 404) { | 280 | if (statusCode === 404) { |
273 | logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url) | 281 | logger.info('Deleting actor %s because there is a 404 in refresh actor.', actor.url) |
274 | actor.Account ? actor.Account.destroy() : actor.VideoChannel.destroy() | 282 | actor.Account |
283 | ? await actor.Account.destroy() | ||
284 | : await actor.VideoChannel.destroy() | ||
285 | |||
275 | return { actor: undefined, refreshed: false } | 286 | return { actor: undefined, refreshed: false } |
276 | } | 287 | } |
277 | 288 | ||
@@ -337,14 +348,14 @@ function saveActorAndServerAndModelIfNotExist ( | |||
337 | ownerActor?: MActorFullActor, | 348 | ownerActor?: MActorFullActor, |
338 | t?: Transaction | 349 | t?: Transaction |
339 | ): Bluebird<MActorFullActor> | Promise<MActorFullActor> { | 350 | ): Bluebird<MActorFullActor> | Promise<MActorFullActor> { |
340 | let actor = result.actor | 351 | const actor = result.actor |
341 | 352 | ||
342 | if (t !== undefined) return save(t) | 353 | if (t !== undefined) return save(t) |
343 | 354 | ||
344 | return sequelizeTypescript.transaction(t => save(t)) | 355 | return sequelizeTypescript.transaction(t => save(t)) |
345 | 356 | ||
346 | async function save (t: Transaction) { | 357 | async function save (t: Transaction) { |
347 | const actorHost = url.parse(actor.url).host | 358 | const actorHost = new URL(actor.url).host |
348 | 359 | ||
349 | const serverOptions = { | 360 | const serverOptions = { |
350 | where: { | 361 | where: { |
@@ -402,7 +413,7 @@ type FetchRemoteActorResult = { | |||
402 | support?: string | 413 | support?: string |
403 | playlists?: string | 414 | playlists?: string |
404 | avatar?: { | 415 | avatar?: { |
405 | name: string, | 416 | name: string |
406 | fileUrl: string | 417 | fileUrl: string |
407 | } | 418 | } |
408 | attributedTo: ActivityPubAttributedTo[] | 419 | attributedTo: ActivityPubAttributedTo[] |
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index f2ab54cf7..551d04ae3 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts | |||
@@ -4,11 +4,11 @@ import { ACTIVITY_PUB } from '../../initializers/constants' | |||
4 | import { ActorModel } from '../../models/activitypub/actor' | 4 | import { ActorModel } from '../../models/activitypub/actor' |
5 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
6 | import { VideoShareModel } from '../../models/video/video-share' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
7 | import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models' | 7 | import { MActorFollowersUrl, MActorLight, MActorUrl, MCommentOwner, MCommentOwnerVideo, MVideoId } from '../../typings/models' |
8 | 8 | ||
9 | function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { | 9 | function getRemoteVideoAudience (accountActor: MActorUrl, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { |
10 | return { | 10 | return { |
11 | to: [ video.VideoChannel.Account.Actor.url ], | 11 | to: [ accountActor.url ], |
12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) | 12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) |
13 | } | 13 | } |
14 | } | 14 | } |
@@ -32,6 +32,8 @@ function getVideoCommentAudience ( | |||
32 | 32 | ||
33 | // Send to actors we reply to | 33 | // Send to actors we reply to |
34 | for (const parentComment of threadParentComments) { | 34 | for (const parentComment of threadParentComments) { |
35 | if (parentComment.isDeleted()) continue | ||
36 | |||
35 | cc.push(parentComment.Account.Actor.url) | 37 | cc.push(parentComment.Account.Actor.url) |
36 | } | 38 | } |
37 | 39 | ||
@@ -48,7 +50,7 @@ function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[ | |||
48 | } | 50 | } |
49 | } | 51 | } |
50 | 52 | ||
51 | async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) { | 53 | async function getActorsInvolvedInVideo (video: MVideoId, t: Transaction) { |
52 | const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) | 54 | const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) |
53 | 55 | ||
54 | const videoAll = video as VideoModel | 56 | const videoAll = video as VideoModel |
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts index 65b2dcb49..8252e95e9 100644 --- a/server/lib/activitypub/cache-file.ts +++ b/server/lib/activitypub/cache-file.ts | |||
@@ -13,7 +13,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject | |||
13 | if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url) | 13 | if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url) |
14 | 14 | ||
15 | return { | 15 | return { |
16 | expiresOn: new Date(cacheFileObject.expires), | 16 | expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null, |
17 | url: cacheFileObject.id, | 17 | url: cacheFileObject.id, |
18 | fileUrl: url.href, | 18 | fileUrl: url.href, |
19 | strategy: null, | 19 | strategy: null, |
@@ -30,7 +30,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject | |||
30 | if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`) | 30 | if (!videoFile) throw new Error(`Cannot find video file ${url.height} ${url.fps} of video ${video.url}`) |
31 | 31 | ||
32 | return { | 32 | return { |
33 | expiresOn: new Date(cacheFileObject.expires), | 33 | expiresOn: cacheFileObject.expires ? new Date(cacheFileObject.expires) : null, |
34 | url: cacheFileObject.id, | 34 | url: cacheFileObject.id, |
35 | fileUrl: url.href, | 35 | fileUrl: url.href, |
36 | strategy: null, | 36 | strategy: null, |
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts index 9e469e3e6..eeafdf4ba 100644 --- a/server/lib/activitypub/crawl.ts +++ b/server/lib/activitypub/crawl.ts | |||
@@ -3,7 +3,7 @@ import { doRequest } from '../../helpers/requests' | |||
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import * as Bluebird from 'bluebird' | 4 | import * as Bluebird from 'bluebird' |
5 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' | 5 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' |
6 | import { parse } from 'url' | 6 | import { URL } from 'url' |
7 | 7 | ||
8 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) | 8 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) |
9 | type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>) | 9 | type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>) |
@@ -24,7 +24,7 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T> | |||
24 | const response = await doRequest<ActivityPubOrderedCollection<T>>(options) | 24 | const response = await doRequest<ActivityPubOrderedCollection<T>>(options) |
25 | const firstBody = response.body | 25 | const firstBody = response.body |
26 | 26 | ||
27 | let limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT | 27 | const limit = ACTIVITY_PUB.FETCH_PAGE_LIMIT |
28 | let i = 0 | 28 | let i = 0 |
29 | let nextLink = firstBody.first | 29 | let nextLink = firstBody.first |
30 | while (nextLink && i < limit) { | 30 | while (nextLink && i < limit) { |
@@ -32,7 +32,7 @@ async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T> | |||
32 | 32 | ||
33 | if (typeof nextLink === 'string') { | 33 | if (typeof nextLink === 'string') { |
34 | // Don't crawl ourselves | 34 | // Don't crawl ourselves |
35 | const remoteHost = parse(nextLink).host | 35 | const remoteHost = new URL(nextLink).host |
36 | if (remoteHost === WEBSERVER.HOST) continue | 36 | if (remoteHost === WEBSERVER.HOST) continue |
37 | 37 | ||
38 | options.uri = nextLink | 38 | options.uri = nextLink |
diff --git a/server/lib/activitypub/follow.ts b/server/lib/activitypub/follow.ts index 1abf43cd4..3b5ad47c9 100644 --- a/server/lib/activitypub/follow.ts +++ b/server/lib/activitypub/follow.ts | |||
@@ -3,8 +3,8 @@ import { CONFIG } from '../../initializers/config' | |||
3 | import { SERVER_ACTOR_NAME } from '../../initializers/constants' | 3 | import { SERVER_ACTOR_NAME } from '../../initializers/constants' |
4 | import { JobQueue } from '../job-queue' | 4 | import { JobQueue } from '../job-queue' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { getServerActor } from '../../helpers/utils' | ||
7 | import { ServerModel } from '../../models/server/server' | 6 | import { ServerModel } from '../../models/server/server' |
7 | import { getServerActor } from '@server/models/application/application' | ||
8 | 8 | ||
9 | async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) { | 9 | async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) { |
10 | if (!CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED) return | 10 | if (!CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED) return |
@@ -27,7 +27,6 @@ async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) { | |||
27 | } | 27 | } |
28 | 28 | ||
29 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | 29 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) |
30 | .catch(err => logger.error('Cannot create auto follow back job for %s.', host, err)) | ||
31 | } | 30 | } |
32 | } | 31 | } |
33 | 32 | ||
diff --git a/server/lib/activitypub/index.ts b/server/lib/activitypub/index.ts deleted file mode 100644 index d8c7d83b7..000000000 --- a/server/lib/activitypub/index.ts +++ /dev/null | |||
@@ -1,9 +0,0 @@ | |||
1 | export * from './process' | ||
2 | export * from './send' | ||
3 | export * from './actor' | ||
4 | export * from './share' | ||
5 | export * from './playlist' | ||
6 | export * from './videos' | ||
7 | export * from './video-comments' | ||
8 | export * from './video-rates' | ||
9 | export * from './url' | ||
diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts index c52b715ef..c1d932a68 100644 --- a/server/lib/activitypub/playlist.ts +++ b/server/lib/activitypub/playlist.ts | |||
@@ -20,7 +20,9 @@ import { MAccountDefault, MAccountId, MVideoId } from '../../typings/models' | |||
20 | import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist' | 20 | import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist' |
21 | 21 | ||
22 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) { | 22 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) { |
23 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED | 23 | const privacy = to.includes(ACTIVITY_PUB.PUBLIC) |
24 | ? VideoPlaylistPrivacy.PUBLIC | ||
25 | : VideoPlaylistPrivacy.UNLISTED | ||
24 | 26 | ||
25 | return { | 27 | return { |
26 | name: playlistObject.name, | 28 | name: playlistObject.name, |
@@ -205,7 +207,7 @@ async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusC | |||
205 | 207 | ||
206 | logger.info('Fetching remote playlist %s.', playlistUrl) | 208 | logger.info('Fetching remote playlist %s.', playlistUrl) |
207 | 209 | ||
208 | const { response, body } = await doRequest(options) | 210 | const { response, body } = await doRequest<any>(options) |
209 | 211 | ||
210 | if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) { | 212 | if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) { |
211 | logger.debug('Remote video playlist JSON is not valid.', { body }) | 213 | logger.debug('Remote video playlist JSON is not valid.', { body }) |
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index 7e22125d5..26427aaa1 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { ActivityAnnounce } from '../../../../shared/models/activitypub' | 1 | import { ActivityAnnounce } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
3 | import { sequelizeTypescript } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers/database' |
4 | import { VideoShareModel } from '../../../models/video/video-share' | 4 | import { VideoShareModel } from '../../../models/video/video-share' |
5 | import { forwardVideoRelatedActivity } from '../send/utils' | 5 | import { forwardVideoRelatedActivity } from '../send/utils' |
6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index bee853721..566bf6992 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -2,7 +2,7 @@ import { ActivityCreate, CacheFileObject, VideoTorrentObject } from '../../../.. | |||
2 | import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' | 2 | import { VideoCommentObject } from '../../../../shared/models/activitypub/objects/video-comment-object' |
3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers/database' |
6 | import { resolveThread } from '../video-comments' | 6 | import { resolveThread } from '../video-comments' |
7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
8 | import { forwardVideoRelatedActivity } from '../send/utils' | 8 | import { forwardVideoRelatedActivity } from '../send/utils' |
@@ -12,6 +12,7 @@ import { PlaylistObject } from '../../../../shared/models/activitypub/objects/pl | |||
12 | import { createOrUpdateVideoPlaylist } from '../playlist' | 12 | import { createOrUpdateVideoPlaylist } from '../playlist' |
13 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 13 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
14 | import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' | 14 | import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' |
15 | import { isRedundancyAccepted } from '@server/lib/redundancy' | ||
15 | 16 | ||
16 | async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { | 17 | async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { |
17 | const { activity, byActor } = options | 18 | const { activity, byActor } = options |
@@ -60,6 +61,8 @@ async function processCreateVideo (activity: ActivityCreate, notify: boolean) { | |||
60 | } | 61 | } |
61 | 62 | ||
62 | async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) { | 63 | async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) { |
64 | if (await isRedundancyAccepted(activity, byActor) !== true) return | ||
65 | |||
63 | const cacheFile = activity.object as CacheFileObject | 66 | const cacheFile = activity.object as CacheFileObject |
64 | 67 | ||
65 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) | 68 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) |
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index e76132f91..7c8dc83e8 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { ActivityDelete } from '../../../../shared/models/activitypub' | 1 | import { ActivityDelete } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers/database' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
6 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
7 | import { VideoCommentModel } from '../../../models/video/video-comment' | 7 | import { VideoCommentModel } from '../../../models/video/video-comment' |
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts index debd8a67c..fcdd0b86e 100644 --- a/server/lib/activitypub/process/process-dislike.ts +++ b/server/lib/activitypub/process/process-dislike.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { ActivityCreate, ActivityDislike } from '../../../../shared' | 1 | import { ActivityCreate, ActivityDislike } from '../../../../shared' |
2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects' | 2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects' |
3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
4 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers/database' |
5 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 5 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
7 | import { forwardVideoRelatedActivity } from '../send/utils' | 7 | import { forwardVideoRelatedActivity } from '../send/utils' |
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index e6e9084de..7337f337c 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts | |||
@@ -2,13 +2,14 @@ import { ActivityCreate, ActivityFlag, VideoAbuseState } from '../../../../share | |||
2 | import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects' | 2 | import { VideoAbuseObject } from '../../../../shared/models/activitypub/objects' |
3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers/database' |
6 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 6 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 7 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
8 | import { Notifier } from '../../notifier' | 8 | import { Notifier } from '../../notifier' |
9 | import { getAPId } from '../../../helpers/activitypub' | 9 | import { getAPId } from '../../../helpers/activitypub' |
10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
11 | import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models' | 11 | import { MActorSignature, MVideoAbuseAccountVideo } from '../../../typings/models' |
12 | import { AccountModel } from '@server/models/account/account' | ||
12 | 13 | ||
13 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { | 14 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { |
14 | const { activity, byActor } = options | 15 | const { activity, byActor } = options |
@@ -36,8 +37,9 @@ async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, | |||
36 | logger.debug('Reporting remote abuse for video %s.', getAPId(object)) | 37 | logger.debug('Reporting remote abuse for video %s.', getAPId(object)) |
37 | 38 | ||
38 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) | 39 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) |
40 | const reporterAccount = await sequelizeTypescript.transaction(async t => AccountModel.load(account.id, t)) | ||
39 | 41 | ||
40 | const videoAbuse = await sequelizeTypescript.transaction(async t => { | 42 | const videoAbuseInstance = await sequelizeTypescript.transaction(async t => { |
41 | const videoAbuseData = { | 43 | const videoAbuseData = { |
42 | reporterAccountId: account.id, | 44 | reporterAccountId: account.id, |
43 | reason: flag.content, | 45 | reason: flag.content, |
@@ -45,15 +47,22 @@ async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, | |||
45 | state: VideoAbuseState.PENDING | 47 | state: VideoAbuseState.PENDING |
46 | } | 48 | } |
47 | 49 | ||
48 | const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo | 50 | const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) |
49 | videoAbuseInstance.Video = video | 51 | videoAbuseInstance.Video = video |
52 | videoAbuseInstance.Account = reporterAccount | ||
50 | 53 | ||
51 | logger.info('Remote abuse for video uuid %s created', flag.object) | 54 | logger.info('Remote abuse for video uuid %s created', flag.object) |
52 | 55 | ||
53 | return videoAbuseInstance | 56 | return videoAbuseInstance |
54 | }) | 57 | }) |
55 | 58 | ||
56 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) | 59 | const videoAbuseJSON = videoAbuseInstance.toFormattedJSON() |
60 | |||
61 | Notifier.Instance.notifyOnNewVideoAbuse({ | ||
62 | videoAbuse: videoAbuseJSON, | ||
63 | videoAbuseInstance, | ||
64 | reporter: reporterAccount.Actor.getIdentifier() | ||
65 | }) | ||
57 | } catch (err) { | 66 | } catch (err) { |
58 | logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) | 67 | logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) |
59 | } | 68 | } |
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index db7fb8568..950d421dd 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts | |||
@@ -1,17 +1,17 @@ | |||
1 | import { ActivityFollow } from '../../../../shared/models/activitypub' | 1 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers/database' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
6 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 6 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
7 | import { sendAccept, sendReject } from '../send' | 7 | import { sendAccept, sendReject } from '../send' |
8 | import { Notifier } from '../../notifier' | 8 | import { Notifier } from '../../notifier' |
9 | import { getAPId } from '../../../helpers/activitypub' | 9 | import { getAPId } from '../../../helpers/activitypub' |
10 | import { getServerActor } from '../../../helpers/utils' | ||
11 | import { CONFIG } from '../../../initializers/config' | 10 | import { CONFIG } from '../../../initializers/config' |
12 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 11 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
13 | import { MActorFollowActors, MActorSignature } from '../../../typings/models' | 12 | import { MActorFollowActors, MActorSignature } from '../../../typings/models' |
14 | import { autoFollowBackIfNeeded } from '../follow' | 13 | import { autoFollowBackIfNeeded } from '../follow' |
14 | import { getServerActor } from '@server/models/application/application' | ||
15 | 15 | ||
16 | async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { | 16 | async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { |
17 | const { activity, byActor } = options | 17 | const { activity, byActor } = options |
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts index 62be0de42..fba3c76a4 100644 --- a/server/lib/activitypub/process/process-like.ts +++ b/server/lib/activitypub/process/process-like.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { ActivityLike } from '../../../../shared/models/activitypub' | 1 | import { ActivityLike } from '../../../../shared/models/activitypub' |
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
3 | import { sequelizeTypescript } from '../../../initializers' | 3 | import { sequelizeTypescript } from '../../../initializers/database' |
4 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 4 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
5 | import { forwardVideoRelatedActivity } from '../send/utils' | 5 | import { forwardVideoRelatedActivity } from '../send/utils' |
6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts index 00e9afa10..9804436a2 100644 --- a/server/lib/activitypub/process/process-reject.ts +++ b/server/lib/activitypub/process/process-reject.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { ActivityReject } from '../../../../shared/models/activitypub/activity' | 1 | import { ActivityReject } from '../../../../shared/models/activitypub/activity' |
2 | import { sequelizeTypescript } from '../../../initializers' | 2 | import { sequelizeTypescript } from '../../../initializers/database' |
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
4 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 4 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
5 | import { MActor } from '../../../typings/models' | 5 | import { MActor } from '../../../typings/models' |
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index 10643b2e9..9ef6a8a97 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts | |||
@@ -2,7 +2,7 @@ import { ActivityAnnounce, ActivityFollow, ActivityLike, ActivityUndo, CacheFile | |||
2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects' | 2 | import { DislikeObject } from '../../../../shared/models/activitypub/objects' |
3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 3 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers/database' |
6 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 6 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
7 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { ActorModel } from '../../../models/activitypub/actor' |
8 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 8 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index a47d605d8..98ab0f83d 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -2,7 +2,7 @@ import { ActivityUpdate, CacheFileObject, VideoTorrentObject } from '../../../.. | |||
2 | import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' | 2 | import { ActivityPubActor } from '../../../../shared/models/activitypub/activitypub-actor' |
3 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' | 3 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../../helpers/database-utils' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers/database' |
6 | import { AccountModel } from '../../../models/account/account' | 6 | import { AccountModel } from '../../../models/account/account' |
7 | import { ActorModel } from '../../../models/activitypub/actor' | 7 | import { ActorModel } from '../../../models/activitypub/actor' |
8 | import { VideoChannelModel } from '../../../models/video/video-channel' | 8 | import { VideoChannelModel } from '../../../models/video/video-channel' |
@@ -16,6 +16,7 @@ import { PlaylistObject } from '../../../../shared/models/activitypub/objects/pl | |||
16 | import { createOrUpdateVideoPlaylist } from '../playlist' | 16 | import { createOrUpdateVideoPlaylist } from '../playlist' |
17 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 17 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
18 | import { MActorSignature, MAccountIdActor } from '../../../typings/models' | 18 | import { MActorSignature, MAccountIdActor } from '../../../typings/models' |
19 | import { isRedundancyAccepted } from '@server/lib/redundancy' | ||
19 | 20 | ||
20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { | 21 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { |
21 | const { activity, byActor } = options | 22 | const { activity, byActor } = options |
@@ -78,6 +79,8 @@ async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpd | |||
78 | } | 79 | } |
79 | 80 | ||
80 | async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) { | 81 | async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) { |
82 | if (await isRedundancyAccepted(activity, byActor) !== true) return | ||
83 | |||
81 | const cacheFileObject = activity.object as CacheFileObject | 84 | const cacheFileObject = activity.object as CacheFileObject |
82 | 85 | ||
83 | if (!isCacheFileObjectValid(cacheFileObject)) { | 86 | if (!isCacheFileObjectValid(cacheFileObject)) { |
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts index df29ee968..b3b6c933d 100644 --- a/server/lib/activitypub/process/process-view.ts +++ b/server/lib/activitypub/process/process-view.ts | |||
@@ -23,7 +23,8 @@ async function processCreateView (activity: ActivityView | ActivityCreate, byAct | |||
23 | 23 | ||
24 | const options = { | 24 | const options = { |
25 | videoObject, | 25 | videoObject, |
26 | fetchType: 'only-video' as 'only-video' | 26 | fetchType: 'only-immutable-attributes' as 'only-immutable-attributes', |
27 | allowRefresh: false as false | ||
27 | } | 28 | } |
28 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) | 29 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) |
29 | 30 | ||
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts index 9f0225b64..c4c6b849b 100644 --- a/server/lib/activitypub/send/send-accept.ts +++ b/server/lib/activitypub/send/send-accept.ts | |||
@@ -5,7 +5,7 @@ import { buildFollowActivity } from './send-follow' | |||
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { MActor, MActorFollowActors } from '../../../typings/models' | 6 | import { MActor, MActorFollowActors } from '../../../typings/models' |
7 | 7 | ||
8 | async function sendAccept (actorFollow: MActorFollowActors) { | 8 | function sendAccept (actorFollow: MActorFollowActors) { |
9 | const follower = actorFollow.ActorFollower | 9 | const follower = actorFollow.ActorFollower |
10 | const me = actorFollow.ActorFollowing | 10 | const me = actorFollow.ActorFollowing |
11 | 11 | ||
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts index a0f33852c..d03b358f1 100644 --- a/server/lib/activitypub/send/send-announce.ts +++ b/server/lib/activitypub/send/send-announce.ts | |||
@@ -28,7 +28,7 @@ async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, | |||
28 | logger.info('Creating job to send announce %s.', videoShare.url) | 28 | logger.info('Creating job to send announce %s.', videoShare.url) |
29 | 29 | ||
30 | const followersException = [ byActor ] | 30 | const followersException = [ byActor ] |
31 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) | 31 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException, 'Announce') |
32 | } | 32 | } |
33 | 33 | ||
34 | function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { | 34 | function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { |
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 1709d8348..e521cabbc 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts | |||
@@ -6,7 +6,6 @@ import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unic | |||
6 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' | 6 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 8 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
9 | import { getServerActor } from '../../../helpers/utils' | ||
10 | import { | 9 | import { |
11 | MActorLight, | 10 | MActorLight, |
12 | MCommentOwnerVideo, | 11 | MCommentOwnerVideo, |
@@ -16,6 +15,8 @@ import { | |||
16 | MVideoRedundancyFileVideo, | 15 | MVideoRedundancyFileVideo, |
17 | MVideoRedundancyStreamingPlaylistVideo | 16 | MVideoRedundancyStreamingPlaylistVideo |
18 | } from '../../../typings/models' | 17 | } from '../../../typings/models' |
18 | import { getServerActor } from '@server/models/application/application' | ||
19 | import { ContextType } from '@shared/models/activitypub/context' | ||
19 | 20 | ||
20 | async function sendCreateVideo (video: MVideoAP, t: Transaction) { | 21 | async function sendCreateVideo (video: MVideoAP, t: Transaction) { |
21 | if (!video.hasPrivacyForFederation()) return undefined | 22 | if (!video.hasPrivacyForFederation()) return undefined |
@@ -42,7 +43,8 @@ async function sendCreateCacheFile ( | |||
42 | byActor, | 43 | byActor, |
43 | video, | 44 | video, |
44 | url: fileRedundancy.url, | 45 | url: fileRedundancy.url, |
45 | object: fileRedundancy.toActivityPubObject() | 46 | object: fileRedundancy.toActivityPubObject(), |
47 | contextType: 'CacheFile' | ||
46 | }) | 48 | }) |
47 | } | 49 | } |
48 | 50 | ||
@@ -78,7 +80,8 @@ async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transacti | |||
78 | // Add the actor that commented too | 80 | // Add the actor that commented too |
79 | actorsInvolvedInComment.push(byActor) | 81 | actorsInvolvedInComment.push(byActor) |
80 | 82 | ||
81 | const parentsCommentActors = threadParentComments.map(c => c.Account.Actor) | 83 | const parentsCommentActors = threadParentComments.filter(c => !c.isDeleted()) |
84 | .map(c => c.Account.Actor) | ||
82 | 85 | ||
83 | let audience: ActivityAudience | 86 | let audience: ActivityAudience |
84 | if (isOrigin) { | 87 | if (isOrigin) { |
@@ -130,11 +133,12 @@ export { | |||
130 | // --------------------------------------------------------------------------- | 133 | // --------------------------------------------------------------------------- |
131 | 134 | ||
132 | async function sendVideoRelatedCreateActivity (options: { | 135 | async function sendVideoRelatedCreateActivity (options: { |
133 | byActor: MActorLight, | 136 | byActor: MActorLight |
134 | video: MVideoAccountLight, | 137 | video: MVideoAccountLight |
135 | url: string, | 138 | url: string |
136 | object: any, | 139 | object: any |
137 | transaction?: Transaction | 140 | transaction?: Transaction |
141 | contextType?: ContextType | ||
138 | }) { | 142 | }) { |
139 | const activityBuilder = (audience: ActivityAudience) => { | 143 | const activityBuilder = (audience: ActivityAudience) => { |
140 | return buildCreateActivity(options.url, options.byActor, options.object, audience) | 144 | return buildCreateActivity(options.url, options.byActor, options.object, audience) |
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts index 3225ebf32..fd3f06dec 100644 --- a/server/lib/activitypub/send/send-delete.ts +++ b/server/lib/activitypub/send/send-delete.ts | |||
@@ -7,9 +7,9 @@ import { getDeleteActivityPubUrl } from '../url' | |||
7 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 7 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
8 | import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' | 8 | import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' |
9 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
10 | import { getServerActor } from '../../../helpers/utils' | ||
11 | import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video' | 10 | import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video' |
12 | import { MActorUrl } from '../../../typings/models' | 11 | import { MActorUrl } from '../../../typings/models' |
12 | import { getServerActor } from '@server/models/application/application' | ||
13 | 13 | ||
14 | async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) { | 14 | async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) { |
15 | logger.info('Creating job to broadcast delete of video %s.', video.url) | 15 | logger.info('Creating job to broadcast delete of video %s.', video.url) |
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts index 6e41f241f..600469c71 100644 --- a/server/lib/activitypub/send/send-dislike.ts +++ b/server/lib/activitypub/send/send-dislike.ts | |||
@@ -6,7 +6,7 @@ import { sendVideoRelatedActivity } from './utils' | |||
6 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' | 7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' |
8 | 8 | ||
9 | async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { | 9 | function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
10 | logger.info('Creating job to dislike %s.', video.url) | 10 | logger.info('Creating job to dislike %s.', video.url) |
11 | 11 | ||
12 | const activityBuilder = (audience: ActivityAudience) => { | 12 | const activityBuilder = (audience: ActivityAudience) => { |
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts index da7638a7b..e4e523631 100644 --- a/server/lib/activitypub/send/send-flag.ts +++ b/server/lib/activitypub/send/send-flag.ts | |||
@@ -7,7 +7,7 @@ import { Transaction } from 'sequelize' | |||
7 | import { MActor, MVideoFullLight } from '../../../typings/models' | 7 | import { MActor, MVideoFullLight } from '../../../typings/models' |
8 | import { MVideoAbuseVideo } from '../../../typings/models/video' | 8 | import { MVideoAbuseVideo } from '../../../typings/models/video' |
9 | 9 | ||
10 | async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { | 10 | function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { |
11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user | 11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user |
12 | 12 | ||
13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | 13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) |
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts index e84a6f98b..5db252325 100644 --- a/server/lib/activitypub/send/send-like.ts +++ b/server/lib/activitypub/send/send-like.ts | |||
@@ -6,7 +6,7 @@ import { audiencify, getAudience } from '../audience' | |||
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' | 7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' |
8 | 8 | ||
9 | async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { | 9 | function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
10 | logger.info('Creating job to like %s.', video.url) | 10 | logger.info('Creating job to like %s.', video.url) |
11 | 11 | ||
12 | const activityBuilder = (audience: ActivityAudience) => { | 12 | const activityBuilder = (audience: ActivityAudience) => { |
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts index 4258a3c36..643c468a9 100644 --- a/server/lib/activitypub/send/send-reject.ts +++ b/server/lib/activitypub/send/send-reject.ts | |||
@@ -5,7 +5,7 @@ import { buildFollowActivity } from './send-follow' | |||
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { MActor } from '../../../typings/models' | 6 | import { MActor } from '../../../typings/models' |
7 | 7 | ||
8 | async function sendReject (follower: MActor, following: MActor) { | 8 | function sendReject (follower: MActor, following: MActor) { |
9 | if (!follower.serverId) { // This should never happen | 9 | if (!follower.serverId) { // This should never happen |
10 | logger.warn('Do not sending reject to local follower.') | 10 | logger.warn('Do not sending reject to local follower.') |
11 | return | 11 | return |
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts index e9ab5b3c5..33f1d4921 100644 --- a/server/lib/activitypub/send/send-undo.ts +++ b/server/lib/activitypub/send/send-undo.ts | |||
@@ -28,7 +28,7 @@ import { | |||
28 | MVideoShare | 28 | MVideoShare |
29 | } from '../../../typings/models' | 29 | } from '../../../typings/models' |
30 | 30 | ||
31 | async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { | 31 | function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { |
32 | const me = actorFollow.ActorFollower | 32 | const me = actorFollow.ActorFollower |
33 | const following = actorFollow.ActorFollowing | 33 | const following = actorFollow.ActorFollowing |
34 | 34 | ||
@@ -118,10 +118,10 @@ function undoActivityData ( | |||
118 | } | 118 | } |
119 | 119 | ||
120 | async function sendUndoVideoRelatedActivity (options: { | 120 | async function sendUndoVideoRelatedActivity (options: { |
121 | byActor: MActor, | 121 | byActor: MActor |
122 | video: MVideoAccountLight, | 122 | video: MVideoAccountLight |
123 | url: string, | 123 | url: string |
124 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, | 124 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce |
125 | transaction: Transaction | 125 | transaction: Transaction |
126 | }) { | 126 | }) { |
127 | const activityBuilder = (audience: ActivityAudience) => { | 127 | const activityBuilder = (audience: ActivityAudience) => { |
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index 9c76671b5..7a4cf3f56 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts | |||
@@ -8,9 +8,7 @@ import { getUpdateActivityPubUrl } from '../url' | |||
8 | import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' | 8 | import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' |
9 | import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' | 9 | import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' |
10 | import { logger } from '../../../helpers/logger' | 10 | import { logger } from '../../../helpers/logger' |
11 | import { VideoCaptionModel } from '../../../models/video/video-caption' | ||
12 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 11 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
13 | import { getServerActor } from '../../../helpers/utils' | ||
14 | import { | 12 | import { |
15 | MAccountDefault, | 13 | MAccountDefault, |
16 | MActor, | 14 | MActor, |
@@ -21,6 +19,7 @@ import { | |||
21 | MVideoPlaylistFull, | 19 | MVideoPlaylistFull, |
22 | MVideoRedundancyVideo | 20 | MVideoRedundancyVideo |
23 | } from '../../../typings/models' | 21 | } from '../../../typings/models' |
22 | import { getServerActor } from '@server/models/application/application' | ||
24 | 23 | ||
25 | async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) { | 24 | async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) { |
26 | const video = videoArg as MVideoAP | 25 | const video = videoArg as MVideoAP |
@@ -29,7 +28,7 @@ async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction | |||
29 | 28 | ||
30 | logger.info('Creating job to update video %s.', video.url) | 29 | logger.info('Creating job to update video %s.', video.url) |
31 | 30 | ||
32 | const byActor = overrodeByActor ? overrodeByActor : video.VideoChannel.Account.Actor | 31 | const byActor = overrodeByActor || video.VideoChannel.Account.Actor |
33 | 32 | ||
34 | const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) | 33 | const url = getUpdateActivityPubUrl(video.url, video.updatedAt.toISOString()) |
35 | 34 | ||
@@ -85,7 +84,7 @@ async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVide | |||
85 | return buildUpdateActivity(url, byActor, redundancyObject, audience) | 84 | return buildUpdateActivity(url, byActor, redundancyObject, audience) |
86 | } | 85 | } |
87 | 86 | ||
88 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) | 87 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, contextType: 'CacheFile' }) |
89 | } | 88 | } |
90 | 89 | ||
91 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { | 90 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { |
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts index 8809417f9..1f864ea52 100644 --- a/server/lib/activitypub/send/send-view.ts +++ b/server/lib/activitypub/send/send-view.ts | |||
@@ -5,9 +5,9 @@ import { getVideoLikeActivityPubUrl } from '../url' | |||
5 | import { sendVideoRelatedActivity } from './utils' | 5 | import { sendVideoRelatedActivity } from './utils' |
6 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models' | 8 | import { MActorAudience, MVideoImmutable, MVideoUrl } from '@server/typings/models' |
9 | 9 | ||
10 | async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) { | 10 | async function sendView (byActor: ActorModel, video: MVideoImmutable, t: Transaction) { |
11 | logger.info('Creating job to send view of %s.', video.url) | 11 | logger.info('Creating job to send view of %s.', video.url) |
12 | 12 | ||
13 | const activityBuilder = (audience: ActivityAudience) => { | 13 | const activityBuilder = (audience: ActivityAudience) => { |
@@ -16,7 +16,7 @@ async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Tran | |||
16 | return buildViewActivity(url, byActor, video, audience) | 16 | return buildViewActivity(url, byActor, video, audience) |
17 | } | 17 | } |
18 | 18 | ||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | 19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t, contextType: 'View' }) |
20 | } | 20 | } |
21 | 21 | ||
22 | function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { | 22 | function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { |
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts index 77b723479..44a8926e5 100644 --- a/server/lib/activitypub/send/utils.ts +++ b/server/lib/activitypub/send/utils.ts | |||
@@ -5,26 +5,30 @@ import { ActorModel } from '../../../models/activitypub/actor' | |||
5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
6 | import { JobQueue } from '../../job-queue' | 6 | import { JobQueue } from '../../job-queue' |
7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' | 7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' |
8 | import { getServerActor } from '../../../helpers/utils' | ||
9 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' | 8 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' |
10 | import { MActorWithInboxes, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models' | 9 | import { MActor, MActorId, MActorLight, MActorWithInboxes, MVideoAccountLight, MVideoId, MVideoImmutable } from '../../../typings/models' |
10 | import { getServerActor } from '@server/models/application/application' | ||
11 | import { ContextType } from '@shared/models/activitypub/context' | ||
11 | 12 | ||
12 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { | 13 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { |
13 | byActor: MActorLight, | 14 | byActor: MActorLight |
14 | video: MVideoAccountLight, | 15 | video: MVideoImmutable | MVideoAccountLight |
15 | transaction?: Transaction | 16 | transaction?: Transaction |
17 | contextType?: ContextType | ||
16 | }) { | 18 | }) { |
17 | const { byActor, video, transaction } = options | 19 | const { byActor, video, transaction, contextType } = options |
18 | 20 | ||
19 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction) | 21 | const actorsInvolvedInVideo = await getActorsInvolvedInVideo(video, transaction) |
20 | 22 | ||
21 | // Send to origin | 23 | // Send to origin |
22 | if (video.isOwned() === false) { | 24 | if (video.isOwned() === false) { |
23 | const audience = getRemoteVideoAudience(video, actorsInvolvedInVideo) | 25 | const accountActor = (video as MVideoAccountLight).VideoChannel?.Account?.Actor || await ActorModel.loadAccountActorByVideoId(video.id) |
26 | |||
27 | const audience = getRemoteVideoAudience(accountActor, actorsInvolvedInVideo) | ||
24 | const activity = activityBuilder(audience) | 28 | const activity = activityBuilder(audience) |
25 | 29 | ||
26 | return afterCommitIfTransaction(transaction, () => { | 30 | return afterCommitIfTransaction(transaction, () => { |
27 | return unicastTo(activity, byActor, video.VideoChannel.Account.Actor.getSharedInbox()) | 31 | return unicastTo(activity, byActor, accountActor.getSharedInbox(), contextType) |
28 | }) | 32 | }) |
29 | } | 33 | } |
30 | 34 | ||
@@ -34,14 +38,14 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud | |||
34 | 38 | ||
35 | const actorsException = [ byActor ] | 39 | const actorsException = [ byActor ] |
36 | 40 | ||
37 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException) | 41 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, transaction, actorsException, contextType) |
38 | } | 42 | } |
39 | 43 | ||
40 | async function forwardVideoRelatedActivity ( | 44 | async function forwardVideoRelatedActivity ( |
41 | activity: Activity, | 45 | activity: Activity, |
42 | t: Transaction, | 46 | t: Transaction, |
43 | followersException: MActorWithInboxes[] = [], | 47 | followersException: MActorWithInboxes[], |
44 | video: MVideo | 48 | video: MVideoId |
45 | ) { | 49 | ) { |
46 | // Mastodon does not add our announces in audience, so we forward to them manually | 50 | // Mastodon does not add our announces in audience, so we forward to them manually |
47 | const additionalActors = await getActorsInvolvedInVideo(video, t) | 51 | const additionalActors = await getActorsInvolvedInVideo(video, t) |
@@ -90,11 +94,12 @@ async function broadcastToFollowers ( | |||
90 | byActor: MActorId, | 94 | byActor: MActorId, |
91 | toFollowersOf: MActorId[], | 95 | toFollowersOf: MActorId[], |
92 | t: Transaction, | 96 | t: Transaction, |
93 | actorsException: MActorWithInboxes[] = [] | 97 | actorsException: MActorWithInboxes[] = [], |
98 | contextType?: ContextType | ||
94 | ) { | 99 | ) { |
95 | const uris = await computeFollowerUris(toFollowersOf, actorsException, t) | 100 | const uris = await computeFollowerUris(toFollowersOf, actorsException, t) |
96 | 101 | ||
97 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) | 102 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) |
98 | } | 103 | } |
99 | 104 | ||
100 | async function broadcastToActors ( | 105 | async function broadcastToActors ( |
@@ -102,13 +107,14 @@ async function broadcastToActors ( | |||
102 | byActor: MActorId, | 107 | byActor: MActorId, |
103 | toActors: MActor[], | 108 | toActors: MActor[], |
104 | t?: Transaction, | 109 | t?: Transaction, |
105 | actorsException: MActorWithInboxes[] = [] | 110 | actorsException: MActorWithInboxes[] = [], |
111 | contextType?: ContextType | ||
106 | ) { | 112 | ) { |
107 | const uris = await computeUris(toActors, actorsException) | 113 | const uris = await computeUris(toActors, actorsException) |
108 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) | 114 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor, contextType)) |
109 | } | 115 | } |
110 | 116 | ||
111 | function broadcastTo (uris: string[], data: any, byActor: MActorId) { | 117 | function broadcastTo (uris: string[], data: any, byActor: MActorId, contextType?: ContextType) { |
112 | if (uris.length === 0) return undefined | 118 | if (uris.length === 0) return undefined |
113 | 119 | ||
114 | logger.debug('Creating broadcast job.', { uris }) | 120 | logger.debug('Creating broadcast job.', { uris }) |
@@ -116,19 +122,21 @@ function broadcastTo (uris: string[], data: any, byActor: MActorId) { | |||
116 | const payload = { | 122 | const payload = { |
117 | uris, | 123 | uris, |
118 | signatureActorId: byActor.id, | 124 | signatureActorId: byActor.id, |
119 | body: data | 125 | body: data, |
126 | contextType | ||
120 | } | 127 | } |
121 | 128 | ||
122 | return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) | 129 | return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) |
123 | } | 130 | } |
124 | 131 | ||
125 | function unicastTo (data: any, byActor: MActorId, toActorUrl: string) { | 132 | function unicastTo (data: any, byActor: MActorId, toActorUrl: string, contextType?: ContextType) { |
126 | logger.debug('Creating unicast job.', { uri: toActorUrl }) | 133 | logger.debug('Creating unicast job.', { uri: toActorUrl }) |
127 | 134 | ||
128 | const payload = { | 135 | const payload = { |
129 | uri: toActorUrl, | 136 | uri: toActorUrl, |
130 | signatureActorId: byActor.id, | 137 | signatureActorId: byActor.id, |
131 | body: data | 138 | body: data, |
139 | contextType | ||
132 | } | 140 | } |
133 | 141 | ||
134 | JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload }) | 142 | JobQueue.Instance.createJob({ type: 'activitypub-http-unicast', payload }) |
@@ -153,7 +161,7 @@ async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: | |||
153 | const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) | 161 | const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) |
154 | const sharedInboxesException = await buildSharedInboxesException(actorsException) | 162 | const sharedInboxesException = await buildSharedInboxesException(actorsException) |
155 | 163 | ||
156 | return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) | 164 | return result.data.filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false) |
157 | } | 165 | } |
158 | 166 | ||
159 | async function computeUris (toActors: MActor[], actorsException: MActorWithInboxes[] = []) { | 167 | async function computeUris (toActors: MActor[], actorsException: MActorWithInboxes[] = []) { |
@@ -166,7 +174,7 @@ async function computeUris (toActors: MActor[], actorsException: MActorWithInbox | |||
166 | 174 | ||
167 | const sharedInboxesException = await buildSharedInboxesException(actorsException) | 175 | const sharedInboxesException = await buildSharedInboxesException(actorsException) |
168 | return Array.from(toActorSharedInboxesSet) | 176 | return Array.from(toActorSharedInboxesSet) |
169 | .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) | 177 | .filter(sharedInbox => sharedInboxesException.includes(sharedInbox) === false) |
170 | } | 178 | } |
171 | 179 | ||
172 | async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) { | 180 | async function buildSharedInboxesException (actorsException: MActorWithInboxes[]) { |
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index e847c4b7d..d2cbc59a8 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { getServerActor } from '../../helpers/utils' | ||
3 | import { VideoShareModel } from '../../models/video/video-share' | 2 | import { VideoShareModel } from '../../models/video/video-share' |
4 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' | 3 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' |
5 | import { getVideoAnnounceActivityPubUrl } from './url' | 4 | import { getVideoAnnounceActivityPubUrl } from './url' |
@@ -10,6 +9,7 @@ import { logger } from '../../helpers/logger' | |||
10 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 9 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
11 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 10 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
12 | import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video' | 11 | import { MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video' |
12 | import { getServerActor } from '@server/models/application/application' | ||
13 | 13 | ||
14 | async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) { | 14 | async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) { |
15 | if (!video.hasPrivacyForFederation()) return undefined | 15 | if (!video.hasPrivacyForFederation()) return undefined |
@@ -36,7 +36,7 @@ async function addVideoShares (shareUrls: string[], video: MVideoId) { | |||
36 | await Bluebird.map(shareUrls, async shareUrl => { | 36 | await Bluebird.map(shareUrls, async shareUrl => { |
37 | try { | 37 | try { |
38 | // Fetch url | 38 | // Fetch url |
39 | const { body } = await doRequest({ | 39 | const { body } = await doRequest<any>({ |
40 | uri: shareUrl, | 40 | uri: shareUrl, |
41 | json: true, | 41 | json: true, |
42 | activityPub: true | 42 | activityPub: true |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index d5c078a29..3aee6799e 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -10,9 +10,9 @@ import { checkUrlsSameHost } from '../../helpers/activitypub' | |||
10 | import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video' | 10 | import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video' |
11 | 11 | ||
12 | type ResolveThreadParams = { | 12 | type ResolveThreadParams = { |
13 | url: string, | 13 | url: string |
14 | comments?: MCommentOwner[], | 14 | comments?: MCommentOwner[] |
15 | isVideo?: boolean, | 15 | isVideo?: boolean |
16 | commentCreated?: boolean | 16 | commentCreated?: boolean |
17 | } | 17 | } |
18 | type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> | 18 | type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> |
@@ -28,7 +28,7 @@ async function resolveThread (params: ResolveThreadParams): ResolveThreadResult | |||
28 | if (params.commentCreated === undefined) params.commentCreated = false | 28 | if (params.commentCreated === undefined) params.commentCreated = false |
29 | if (params.comments === undefined) params.comments = [] | 29 | if (params.comments === undefined) params.comments = [] |
30 | 30 | ||
31 | // Already have this comment? | 31 | // Already have this comment? |
32 | if (isVideo !== true) { | 32 | if (isVideo !== true) { |
33 | const result = await resolveCommentFromDB(params) | 33 | const result = await resolveCommentFromDB(params) |
34 | if (result) return result | 34 | if (result) return result |
@@ -87,7 +87,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
87 | 87 | ||
88 | let resultComment: MCommentOwnerVideo | 88 | let resultComment: MCommentOwnerVideo |
89 | if (comments.length !== 0) { | 89 | if (comments.length !== 0) { |
90 | const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo | 90 | const firstReply = comments[comments.length - 1] as MCommentOwnerVideo |
91 | firstReply.inReplyToCommentId = null | 91 | firstReply.inReplyToCommentId = null |
92 | firstReply.originCommentId = null | 92 | firstReply.originCommentId = null |
93 | firstReply.videoId = video.id | 93 | firstReply.videoId = video.id |
@@ -97,9 +97,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
97 | comments[comments.length - 1] = await firstReply.save() | 97 | comments[comments.length - 1] = await firstReply.save() |
98 | 98 | ||
99 | for (let i = comments.length - 2; i >= 0; i--) { | 99 | for (let i = comments.length - 2; i >= 0; i--) { |
100 | const comment = comments[ i ] as MCommentOwnerVideo | 100 | const comment = comments[i] as MCommentOwnerVideo |
101 | comment.originCommentId = firstReply.id | 101 | comment.originCommentId = firstReply.id |
102 | comment.inReplyToCommentId = comments[ i + 1 ].id | 102 | comment.inReplyToCommentId = comments[i + 1].id |
103 | comment.videoId = video.id | 103 | comment.videoId = video.id |
104 | comment.changed('updatedAt', true) | 104 | comment.changed('updatedAt', true) |
105 | comment.Video = video | 105 | comment.Video = video |
@@ -120,7 +120,7 @@ async function resolveParentComment (params: ResolveThreadParams) { | |||
120 | throw new Error('Recursion limit reached when resolving a thread') | 120 | throw new Error('Recursion limit reached when resolving a thread') |
121 | } | 121 | } |
122 | 122 | ||
123 | const { body } = await doRequest({ | 123 | const { body } = await doRequest<any>({ |
124 | uri: url, | 124 | uri: url, |
125 | json: true, | 125 | json: true, |
126 | activityPub: true | 126 | activityPub: true |
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts index 6bd46bb58..202368c8f 100644 --- a/server/lib/activitypub/video-rates.ts +++ b/server/lib/activitypub/video-rates.ts | |||
@@ -18,7 +18,7 @@ async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateTy | |||
18 | await Bluebird.map(ratesUrl, async rateUrl => { | 18 | await Bluebird.map(ratesUrl, async rateUrl => { |
19 | try { | 19 | try { |
20 | // Fetch url | 20 | // Fetch url |
21 | const { body } = await doRequest({ | 21 | const { body } = await doRequest<any>({ |
22 | uri: rateUrl, | 22 | uri: rateUrl, |
23 | json: true, | 23 | json: true, |
24 | activityPub: true | 24 | activityPub: true |
@@ -58,8 +58,6 @@ async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateTy | |||
58 | const field = rate === 'like' ? 'likes' : 'dislikes' | 58 | const field = rate === 'like' ? 'likes' : 'dislikes' |
59 | await video.increment(field, { by: rateCounts }) | 59 | await video.increment(field, { by: rateCounts }) |
60 | } | 60 | } |
61 | |||
62 | return | ||
63 | } | 61 | } |
64 | 62 | ||
65 | async function sendVideoRateChange ( | 63 | async function sendVideoRateChange ( |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index ade93150f..7d16bd390 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -6,25 +6,27 @@ import { | |||
6 | ActivityHashTagObject, | 6 | ActivityHashTagObject, |
7 | ActivityMagnetUrlObject, | 7 | ActivityMagnetUrlObject, |
8 | ActivityPlaylistSegmentHashesObject, | 8 | ActivityPlaylistSegmentHashesObject, |
9 | ActivityPlaylistUrlObject, ActivityTagObject, | 9 | ActivityPlaylistUrlObject, ActivitypubHttpFetcherPayload, |
10 | ActivityTagObject, | ||
10 | ActivityUrlObject, | 11 | ActivityUrlObject, |
11 | ActivityVideoUrlObject, | 12 | ActivityVideoUrlObject, |
12 | VideoState | 13 | VideoState |
13 | } from '../../../shared/index' | 14 | } from '../../../shared/index' |
14 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 15 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
15 | import { VideoPrivacy } from '../../../shared/models/videos' | 16 | import { VideoPrivacy } from '../../../shared/models/videos' |
16 | import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' | 17 | import { isAPVideoFileMetadataObject, sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' |
17 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 18 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' |
18 | import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' | 19 | import { deleteNonExistingModels, resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' |
19 | import { logger } from '../../helpers/logger' | 20 | import { logger } from '../../helpers/logger' |
20 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' | 21 | import { doRequest } from '../../helpers/requests' |
21 | import { | 22 | import { |
22 | ACTIVITY_PUB, | 23 | ACTIVITY_PUB, |
23 | MIMETYPES, | 24 | MIMETYPES, |
24 | P2P_MEDIA_LOADER_PEER_VERSION, | 25 | P2P_MEDIA_LOADER_PEER_VERSION, |
25 | PREVIEWS_SIZE, | 26 | PREVIEWS_SIZE, |
26 | REMOTE_SCHEME, | 27 | REMOTE_SCHEME, |
27 | STATIC_PATHS | 28 | STATIC_PATHS, |
29 | THUMBNAILS_SIZE | ||
28 | } from '../../initializers/constants' | 30 | } from '../../initializers/constants' |
29 | import { TagModel } from '../../models/video/tag' | 31 | import { TagModel } from '../../models/video/tag' |
30 | import { VideoModel } from '../../models/video/video' | 32 | import { VideoModel } from '../../models/video/video' |
@@ -36,11 +38,10 @@ import { sendCreateVideo, sendUpdateVideo } from './send' | |||
36 | import { isArray } from '../../helpers/custom-validators/misc' | 38 | import { isArray } from '../../helpers/custom-validators/misc' |
37 | import { VideoCaptionModel } from '../../models/video/video-caption' | 39 | import { VideoCaptionModel } from '../../models/video/video-caption' |
38 | import { JobQueue } from '../job-queue' | 40 | import { JobQueue } from '../job-queue' |
39 | import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' | ||
40 | import { createRates } from './video-rates' | 41 | import { createRates } from './video-rates' |
41 | import { addVideoShares, shareVideoByServerAndChannel } from './share' | 42 | import { addVideoShares, shareVideoByServerAndChannel } from './share' |
42 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' | 43 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' |
43 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 44 | import { buildRemoteVideoBaseUrl, checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
44 | import { Notifier } from '../notifier' | 45 | import { Notifier } from '../notifier' |
45 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | 46 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' |
46 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 47 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
@@ -68,9 +69,11 @@ import { | |||
68 | MVideoFile, | 69 | MVideoFile, |
69 | MVideoFullLight, | 70 | MVideoFullLight, |
70 | MVideoId, | 71 | MVideoId, |
72 | MVideoImmutable, | ||
71 | MVideoThumbnail | 73 | MVideoThumbnail |
72 | } from '../../typings/models' | 74 | } from '../../typings/models' |
73 | import { MThumbnail } from '../../typings/models/video/thumbnail' | 75 | import { MThumbnail } from '../../typings/models/video/thumbnail' |
76 | import { maxBy, minBy } from 'lodash' | ||
74 | 77 | ||
75 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 78 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { |
76 | const video = videoArg as MVideoAP | 79 | const video = videoArg as MVideoAP |
@@ -109,7 +112,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request. | |||
109 | 112 | ||
110 | logger.info('Fetching remote video %s.', videoUrl) | 113 | logger.info('Fetching remote video %s.', videoUrl) |
111 | 114 | ||
112 | const { response, body } = await doRequest(options) | 115 | const { response, body } = await doRequest<any>(options) |
113 | 116 | ||
114 | if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) { | 117 | if (sanitizeAndCheckVideoTorrentObject(body) === false || checkUrlsSameHost(body.id, videoUrl) !== true) { |
115 | logger.debug('Remote video JSON is not valid.', { body }) | 118 | logger.debug('Remote video JSON is not valid.', { body }) |
@@ -127,23 +130,10 @@ async function fetchRemoteVideoDescription (video: MVideoAccountLight) { | |||
127 | json: true | 130 | json: true |
128 | } | 131 | } |
129 | 132 | ||
130 | const { body } = await doRequest(options) | 133 | const { body } = await doRequest<any>(options) |
131 | return body.description ? body.description : '' | 134 | return body.description ? body.description : '' |
132 | } | 135 | } |
133 | 136 | ||
134 | function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) { | ||
135 | const url = buildRemoteBaseUrl(video, path) | ||
136 | |||
137 | // We need to provide a callback, if no we could have an uncaught exception | ||
138 | return doRequestAndSaveToFile({ uri: url }, destPath) | ||
139 | } | ||
140 | |||
141 | function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) { | ||
142 | const host = video.VideoChannel.Account.Actor.Server.host | ||
143 | |||
144 | return REMOTE_SCHEME.HTTP + '://' + host + path | ||
145 | } | ||
146 | |||
147 | function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { | 137 | function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { |
148 | const channel = videoObject.attributedTo.find(a => a.type === 'Group') | 138 | const channel = videoObject.attributedTo.find(a => a.type === 'Group') |
149 | if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url) | 139 | if (!channel) throw new Error('Cannot find associated video channel to video ' + videoObject.url) |
@@ -173,7 +163,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
173 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate) | 163 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate) |
174 | 164 | ||
175 | await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner) | 165 | await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner) |
176 | .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err })) | 166 | .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err, rootUrl: fetchedVideo.likes })) |
177 | } else { | 167 | } else { |
178 | jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) | 168 | jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) |
179 | } | 169 | } |
@@ -183,7 +173,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
183 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate) | 173 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate) |
184 | 174 | ||
185 | await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner) | 175 | await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner) |
186 | .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err })) | 176 | .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err, rootUrl: fetchedVideo.dislikes })) |
187 | } else { | 177 | } else { |
188 | jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) | 178 | jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) |
189 | } | 179 | } |
@@ -193,7 +183,7 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
193 | const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate) | 183 | const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate) |
194 | 184 | ||
195 | await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner) | 185 | await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner) |
196 | .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err })) | 186 | .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err, rootUrl: fetchedVideo.shares })) |
197 | } else { | 187 | } else { |
198 | jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) | 188 | jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) |
199 | } | 189 | } |
@@ -203,32 +193,49 @@ async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTo | |||
203 | const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate) | 193 | const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate) |
204 | 194 | ||
205 | await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner) | 195 | await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner) |
206 | .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err })) | 196 | .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err, rootUrl: fetchedVideo.comments })) |
207 | } else { | 197 | } else { |
208 | jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' }) | 198 | jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' }) |
209 | } | 199 | } |
210 | 200 | ||
211 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) | 201 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJobWithPromise({ type: 'activitypub-http-fetcher', payload })) |
212 | } | 202 | } |
213 | 203 | ||
214 | function getOrCreateVideoAndAccountAndChannel (options: { | 204 | type GetVideoResult <T> = Promise<{ |
215 | videoObject: { id: string } | string, | 205 | video: T |
216 | syncParam?: SyncParam, | 206 | created: boolean |
217 | fetchType?: 'all', | 207 | autoBlacklisted?: boolean |
208 | }> | ||
209 | |||
210 | type GetVideoParamAll = { | ||
211 | videoObject: { id: string } | string | ||
212 | syncParam?: SyncParam | ||
213 | fetchType?: 'all' | ||
218 | allowRefresh?: boolean | 214 | allowRefresh?: boolean |
219 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }> | 215 | } |
220 | function getOrCreateVideoAndAccountAndChannel (options: { | 216 | |
221 | videoObject: { id: string } | string, | 217 | type GetVideoParamImmutable = { |
222 | syncParam?: SyncParam, | 218 | videoObject: { id: string } | string |
223 | fetchType?: VideoFetchByUrlType, | 219 | syncParam?: SyncParam |
220 | fetchType: 'only-immutable-attributes' | ||
221 | allowRefresh: false | ||
222 | } | ||
223 | |||
224 | type GetVideoParamOther = { | ||
225 | videoObject: { id: string } | string | ||
226 | syncParam?: SyncParam | ||
227 | fetchType?: 'all' | 'only-video' | ||
224 | allowRefresh?: boolean | 228 | allowRefresh?: boolean |
225 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> | 229 | } |
226 | async function getOrCreateVideoAndAccountAndChannel (options: { | 230 | |
227 | videoObject: { id: string } | string, | 231 | function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamAll): GetVideoResult<MVideoAccountLightBlacklistAllFiles> |
228 | syncParam?: SyncParam, | 232 | function getOrCreateVideoAndAccountAndChannel (options: GetVideoParamImmutable): GetVideoResult<MVideoImmutable> |
229 | fetchType?: VideoFetchByUrlType, | 233 | function getOrCreateVideoAndAccountAndChannel ( |
230 | allowRefresh?: boolean // true by default | 234 | options: GetVideoParamOther |
231 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> { | 235 | ): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> |
236 | async function getOrCreateVideoAndAccountAndChannel ( | ||
237 | options: GetVideoParamAll | GetVideoParamImmutable | GetVideoParamOther | ||
238 | ): GetVideoResult<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail | MVideoImmutable> { | ||
232 | // Default params | 239 | // Default params |
233 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } | 240 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } |
234 | const fetchType = options.fetchType || 'all' | 241 | const fetchType = options.fetchType || 'all' |
@@ -236,18 +243,25 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
236 | 243 | ||
237 | // Get video url | 244 | // Get video url |
238 | const videoUrl = getAPId(options.videoObject) | 245 | const videoUrl = getAPId(options.videoObject) |
239 | |||
240 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) | 246 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) |
247 | |||
241 | if (videoFromDatabase) { | 248 | if (videoFromDatabase) { |
242 | if (videoFromDatabase.isOutdated() && allowRefresh === true) { | 249 | // If allowRefresh is true, we could not call this function using 'only-immutable-attributes' fetch type |
250 | if (allowRefresh === true && (videoFromDatabase as MVideoThumbnail).isOutdated()) { | ||
243 | const refreshOptions = { | 251 | const refreshOptions = { |
244 | video: videoFromDatabase, | 252 | video: videoFromDatabase as MVideoThumbnail, |
245 | fetchedType: fetchType, | 253 | fetchedType: fetchType, |
246 | syncParam | 254 | syncParam |
247 | } | 255 | } |
248 | 256 | ||
249 | if (syncParam.refreshVideo === true) videoFromDatabase = await refreshVideoIfNeeded(refreshOptions) | 257 | if (syncParam.refreshVideo === true) { |
250 | else await JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoFromDatabase.url } }) | 258 | videoFromDatabase = await refreshVideoIfNeeded(refreshOptions) |
259 | } else { | ||
260 | await JobQueue.Instance.createJobWithPromise({ | ||
261 | type: 'activitypub-refresher', | ||
262 | payload: { type: 'video', url: videoFromDatabase.url } | ||
263 | }) | ||
264 | } | ||
251 | } | 265 | } |
252 | 266 | ||
253 | return { video: videoFromDatabase, created: false } | 267 | return { video: videoFromDatabase, created: false } |
@@ -266,10 +280,10 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
266 | } | 280 | } |
267 | 281 | ||
268 | async function updateVideoFromAP (options: { | 282 | async function updateVideoFromAP (options: { |
269 | video: MVideoAccountLightBlacklistAllFiles, | 283 | video: MVideoAccountLightBlacklistAllFiles |
270 | videoObject: VideoTorrentObject, | 284 | videoObject: VideoTorrentObject |
271 | account: MAccountIdActor, | 285 | account: MAccountIdActor |
272 | channel: MChannelDefault, | 286 | channel: MChannelDefault |
273 | overrideTo?: string[] | 287 | overrideTo?: string[] |
274 | }) { | 288 | }) { |
275 | const { video, videoObject, account, channel, overrideTo } = options | 289 | const { video, videoObject, account, channel, overrideTo } = options |
@@ -284,7 +298,7 @@ async function updateVideoFromAP (options: { | |||
284 | let thumbnailModel: MThumbnail | 298 | let thumbnailModel: MThumbnail |
285 | 299 | ||
286 | try { | 300 | try { |
287 | thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | 301 | thumbnailModel = await createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE) |
288 | } catch (err) { | 302 | } catch (err) { |
289 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) | 303 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) |
290 | } | 304 | } |
@@ -300,7 +314,7 @@ async function updateVideoFromAP (options: { | |||
300 | throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url) | 314 | throw new Error('Account ' + account.Actor.url + ' does not own video channel ' + videoChannel.Actor.url) |
301 | } | 315 | } |
302 | 316 | ||
303 | const to = overrideTo ? overrideTo : videoObject.to | 317 | const to = overrideTo || videoObject.to |
304 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to) | 318 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, to) |
305 | video.name = videoData.name | 319 | video.name = videoData.name |
306 | video.uuid = videoData.uuid | 320 | video.uuid = videoData.uuid |
@@ -327,10 +341,11 @@ async function updateVideoFromAP (options: { | |||
327 | 341 | ||
328 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) | 342 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) |
329 | 343 | ||
330 | // FIXME: use icon URL instead | 344 | if (videoUpdated.getPreview()) { |
331 | const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename)) | 345 | const previewUrl = videoUpdated.getPreview().getFileUrl(videoUpdated) |
332 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 346 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
333 | await videoUpdated.addAndSaveThumbnail(previewModel, t) | 347 | await videoUpdated.addAndSaveThumbnail(previewModel, t) |
348 | } | ||
334 | 349 | ||
335 | { | 350 | { |
336 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject.url) | 351 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject.url) |
@@ -391,7 +406,7 @@ async function updateVideoFromAP (options: { | |||
391 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) | 406 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) |
392 | 407 | ||
393 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 408 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
394 | return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t) | 409 | return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, c.url, t) |
395 | }) | 410 | }) |
396 | await Promise.all(videoCaptionsPromises) | 411 | await Promise.all(videoCaptionsPromises) |
397 | } | 412 | } |
@@ -424,8 +439,8 @@ async function updateVideoFromAP (options: { | |||
424 | } | 439 | } |
425 | 440 | ||
426 | async function refreshVideoIfNeeded (options: { | 441 | async function refreshVideoIfNeeded (options: { |
427 | video: MVideoThumbnail, | 442 | video: MVideoThumbnail |
428 | fetchedType: VideoFetchByUrlType, | 443 | fetchedType: VideoFetchByUrlType |
429 | syncParam: SyncParam | 444 | syncParam: SyncParam |
430 | }): Promise<MVideoThumbnail> { | 445 | }): Promise<MVideoThumbnail> { |
431 | if (!options.video.isOutdated()) return options.video | 446 | if (!options.video.isOutdated()) return options.video |
@@ -483,7 +498,6 @@ export { | |||
483 | federateVideoIfNeeded, | 498 | federateVideoIfNeeded, |
484 | fetchRemoteVideo, | 499 | fetchRemoteVideo, |
485 | getOrCreateVideoAndAccountAndChannel, | 500 | getOrCreateVideoAndAccountAndChannel, |
486 | fetchRemoteVideoStaticFile, | ||
487 | fetchRemoteVideoDescription, | 501 | fetchRemoteVideoDescription, |
488 | getOrCreateVideoChannelFromVideoObject | 502 | getOrCreateVideoChannelFromVideoObject |
489 | } | 503 | } |
@@ -494,7 +508,7 @@ function isAPVideoUrlObject (url: any): url is ActivityVideoUrlObject { | |||
494 | const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) | 508 | const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) |
495 | 509 | ||
496 | const urlMediaType = url.mediaType | 510 | const urlMediaType = url.mediaType |
497 | return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/') | 511 | return mimeTypes.includes(urlMediaType) && urlMediaType.startsWith('video/') |
498 | } | 512 | } |
499 | 513 | ||
500 | function isAPStreamingPlaylistUrlObject (url: ActivityUrlObject): url is ActivityPlaylistUrlObject { | 514 | function isAPStreamingPlaylistUrlObject (url: ActivityUrlObject): url is ActivityPlaylistUrlObject { |
@@ -519,7 +533,11 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
519 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) | 533 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) |
520 | const video = VideoModel.build(videoData) as MVideoThumbnail | 534 | const video = VideoModel.build(videoData) as MVideoThumbnail |
521 | 535 | ||
522 | const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | 536 | const promiseThumbnail = createVideoMiniatureFromUrl(getThumbnailFromIcons(videoObject).url, video, ThumbnailType.MINIATURE) |
537 | .catch(err => { | ||
538 | logger.error('Cannot create miniature from url.', { err }) | ||
539 | return undefined | ||
540 | }) | ||
523 | 541 | ||
524 | let thumbnailModel: MThumbnail | 542 | let thumbnailModel: MThumbnail |
525 | if (waitThumbnail === true) { | 543 | if (waitThumbnail === true) { |
@@ -534,9 +552,12 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
534 | 552 | ||
535 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 553 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
536 | 554 | ||
537 | // FIXME: use icon URL instead | 555 | const previewIcon = getPreviewFromIcons(videoObject) |
538 | const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | 556 | const previewUrl = previewIcon |
539 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 557 | ? previewIcon.url |
558 | : buildRemoteVideoBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | ||
559 | const previewModel = createPlaceholderThumbnail(previewUrl, videoCreated, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | ||
560 | |||
540 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) | 561 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) |
541 | 562 | ||
542 | // Process files | 563 | // Process files |
@@ -567,7 +588,7 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
567 | 588 | ||
568 | // Process captions | 589 | // Process captions |
569 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 590 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
570 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) | 591 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, c.url, t) |
571 | }) | 592 | }) |
572 | await Promise.all(videoCaptionsPromises) | 593 | await Promise.all(videoCaptionsPromises) |
573 | 594 | ||
@@ -588,7 +609,11 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
588 | }) | 609 | }) |
589 | 610 | ||
590 | if (waitThumbnail === false) { | 611 | if (waitThumbnail === false) { |
612 | // Error is already caught above | ||
613 | // eslint-disable-next-line @typescript-eslint/no-floating-promises | ||
591 | promiseThumbnail.then(thumbnailModel => { | 614 | promiseThumbnail.then(thumbnailModel => { |
615 | if (!thumbnailModel) return | ||
616 | |||
592 | thumbnailModel = videoCreated.id | 617 | thumbnailModel = videoCreated.id |
593 | 618 | ||
594 | return thumbnailModel.save() | 619 | return thumbnailModel.save() |
@@ -598,24 +623,21 @@ async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAc | |||
598 | return { autoBlacklisted, videoCreated } | 623 | return { autoBlacklisted, videoCreated } |
599 | } | 624 | } |
600 | 625 | ||
601 | async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { | 626 | function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { |
602 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED | 627 | const privacy = to.includes(ACTIVITY_PUB.PUBLIC) |
603 | const duration = videoObject.duration.replace(/[^\d]+/, '') | 628 | ? VideoPrivacy.PUBLIC |
629 | : VideoPrivacy.UNLISTED | ||
604 | 630 | ||
605 | let language: string | undefined | 631 | const duration = videoObject.duration.replace(/[^\d]+/, '') |
606 | if (videoObject.language) { | 632 | const language = videoObject.language?.identifier |
607 | language = videoObject.language.identifier | ||
608 | } | ||
609 | 633 | ||
610 | let category: number | undefined | 634 | const category = videoObject.category |
611 | if (videoObject.category) { | 635 | ? parseInt(videoObject.category.identifier, 10) |
612 | category = parseInt(videoObject.category.identifier, 10) | 636 | : undefined |
613 | } | ||
614 | 637 | ||
615 | let licence: number | undefined | 638 | const licence = videoObject.licence |
616 | if (videoObject.licence) { | 639 | ? parseInt(videoObject.licence.identifier, 10) |
617 | licence = parseInt(videoObject.licence.identifier, 10) | 640 | : undefined |
618 | } | ||
619 | 641 | ||
620 | const description = videoObject.content || null | 642 | const description = videoObject.content || null |
621 | const support = videoObject.support || null | 643 | const support = videoObject.support || null |
@@ -638,8 +660,11 @@ async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, vide | |||
638 | duration: parseInt(duration, 10), | 660 | duration: parseInt(duration, 10), |
639 | createdAt: new Date(videoObject.published), | 661 | createdAt: new Date(videoObject.published), |
640 | publishedAt: new Date(videoObject.published), | 662 | publishedAt: new Date(videoObject.published), |
641 | originallyPublishedAt: videoObject.originallyPublishedAt ? new Date(videoObject.originallyPublishedAt) : null, | 663 | |
642 | // FIXME: updatedAt does not seems to be considered by Sequelize | 664 | originallyPublishedAt: videoObject.originallyPublishedAt |
665 | ? new Date(videoObject.originallyPublishedAt) | ||
666 | : null, | ||
667 | |||
643 | updatedAt: new Date(videoObject.updated), | 668 | updatedAt: new Date(videoObject.updated), |
644 | views: videoObject.views, | 669 | views: videoObject.views, |
645 | likes: 0, | 670 | likes: 0, |
@@ -670,13 +695,22 @@ function videoFileActivityUrlToDBAttributes ( | |||
670 | throw new Error('Cannot parse magnet URI ' + magnet.href) | 695 | throw new Error('Cannot parse magnet URI ' + magnet.href) |
671 | } | 696 | } |
672 | 697 | ||
698 | // Fetch associated metadata url, if any | ||
699 | const metadata = urls.filter(isAPVideoFileMetadataObject) | ||
700 | .find(u => { | ||
701 | return u.height === fileUrl.height && | ||
702 | u.fps === fileUrl.fps && | ||
703 | u.rel.includes(fileUrl.mediaType) | ||
704 | }) | ||
705 | |||
673 | const mediaType = fileUrl.mediaType | 706 | const mediaType = fileUrl.mediaType |
674 | const attribute = { | 707 | const attribute = { |
675 | extname: MIMETYPES.VIDEO.MIMETYPE_EXT[ mediaType ], | 708 | extname: MIMETYPES.VIDEO.MIMETYPE_EXT[mediaType], |
676 | infoHash: parsed.infoHash, | 709 | infoHash: parsed.infoHash, |
677 | resolution: fileUrl.height, | 710 | resolution: fileUrl.height, |
678 | size: fileUrl.size, | 711 | size: fileUrl.size, |
679 | fps: fileUrl.fps || -1, | 712 | fps: fileUrl.fps || -1, |
713 | metadataUrl: metadata?.href, | ||
680 | 714 | ||
681 | // This is a video file owned by a video or by a streaming playlist | 715 | // This is a video file owned by a video or by a streaming playlist |
682 | videoId: (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? null : videoOrPlaylist.id, | 716 | videoId: (videoOrPlaylist as MStreamingPlaylist).playlistUrl ? null : videoOrPlaylist.id, |
@@ -722,3 +756,19 @@ function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObjec | |||
722 | 756 | ||
723 | return attributes | 757 | return attributes |
724 | } | 758 | } |
759 | |||
760 | function getThumbnailFromIcons (videoObject: VideoTorrentObject) { | ||
761 | let validIcons = videoObject.icon.filter(i => i.width > THUMBNAILS_SIZE.minWidth) | ||
762 | // Fallback if there are not valid icons | ||
763 | if (validIcons.length === 0) validIcons = videoObject.icon | ||
764 | |||
765 | return minBy(validIcons, 'width') | ||
766 | } | ||
767 | |||
768 | function getPreviewFromIcons (videoObject: VideoTorrentObject) { | ||
769 | const validIcons = videoObject.icon.filter(i => i.width > PREVIEWS_SIZE.minWidth) | ||
770 | |||
771 | // FIXME: don't put a fallback here for compatibility with PeerTube <2.2 | ||
772 | |||
773 | return maxBy(validIcons, 'width') | ||
774 | } | ||
diff --git a/server/lib/auth.ts b/server/lib/auth.ts new file mode 100644 index 000000000..8579bdbb4 --- /dev/null +++ b/server/lib/auth.ts | |||
@@ -0,0 +1,286 @@ | |||
1 | import { isUserDisplayNameValid, isUserRoleValid, isUserUsernameValid } from '@server/helpers/custom-validators/users' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | import { generateRandomString } from '@server/helpers/utils' | ||
4 | import { OAUTH_LIFETIME, PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME } from '@server/initializers/constants' | ||
5 | import { revokeToken } from '@server/lib/oauth-model' | ||
6 | import { PluginManager } from '@server/lib/plugins/plugin-manager' | ||
7 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' | ||
8 | import { UserRole } from '@shared/models' | ||
9 | import { | ||
10 | RegisterServerAuthenticatedResult, | ||
11 | RegisterServerAuthPassOptions, | ||
12 | RegisterServerExternalAuthenticatedResult | ||
13 | } from '@shared/models/plugins/register-server-auth.model' | ||
14 | import * as express from 'express' | ||
15 | import * as OAuthServer from 'express-oauth-server' | ||
16 | |||
17 | const oAuthServer = new OAuthServer({ | ||
18 | useErrorHandler: true, | ||
19 | accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN, | ||
20 | refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN, | ||
21 | continueMiddleware: true, | ||
22 | model: require('./oauth-model') | ||
23 | }) | ||
24 | |||
25 | // Token is the key, expiration date is the value | ||
26 | const authBypassTokens = new Map<string, { | ||
27 | expires: Date | ||
28 | user: { | ||
29 | username: string | ||
30 | email: string | ||
31 | displayName: string | ||
32 | role: UserRole | ||
33 | } | ||
34 | authName: string | ||
35 | npmName: string | ||
36 | }>() | ||
37 | |||
38 | async function handleLogin (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
39 | const grantType = req.body.grant_type | ||
40 | |||
41 | if (grantType === 'password') { | ||
42 | if (req.body.externalAuthToken) proxifyExternalAuthBypass(req, res) | ||
43 | else await proxifyPasswordGrant(req, res) | ||
44 | } else if (grantType === 'refresh_token') { | ||
45 | await proxifyRefreshGrant(req, res) | ||
46 | } | ||
47 | |||
48 | return forwardTokenReq(req, res, next) | ||
49 | } | ||
50 | |||
51 | async function handleTokenRevocation (req: express.Request, res: express.Response) { | ||
52 | const token = res.locals.oauth.token | ||
53 | |||
54 | res.locals.explicitLogout = true | ||
55 | await revokeToken(token) | ||
56 | |||
57 | // FIXME: uncomment when https://github.com/oauthjs/node-oauth2-server/pull/289 is released | ||
58 | // oAuthServer.revoke(req, res, err => { | ||
59 | // if (err) { | ||
60 | // logger.warn('Error in revoke token handler.', { err }) | ||
61 | // | ||
62 | // return res.status(err.status) | ||
63 | // .json({ | ||
64 | // error: err.message, | ||
65 | // code: err.name | ||
66 | // }) | ||
67 | // .end() | ||
68 | // } | ||
69 | // }) | ||
70 | |||
71 | return res.json() | ||
72 | } | ||
73 | |||
74 | async function onExternalUserAuthenticated (options: { | ||
75 | npmName: string | ||
76 | authName: string | ||
77 | authResult: RegisterServerExternalAuthenticatedResult | ||
78 | }) { | ||
79 | const { npmName, authName, authResult } = options | ||
80 | |||
81 | if (!authResult.req || !authResult.res) { | ||
82 | logger.error('Cannot authenticate external user for auth %s of plugin %s: no req or res are provided.', authName, npmName) | ||
83 | return | ||
84 | } | ||
85 | |||
86 | const { res } = authResult | ||
87 | |||
88 | if (!isAuthResultValid(npmName, authName, authResult)) { | ||
89 | res.redirect('/login?externalAuthError=true') | ||
90 | return | ||
91 | } | ||
92 | |||
93 | logger.info('Generating auth bypass token for %s in auth %s of plugin %s.', authResult.username, authName, npmName) | ||
94 | |||
95 | const bypassToken = await generateRandomString(32) | ||
96 | |||
97 | const expires = new Date() | ||
98 | expires.setTime(expires.getTime() + PLUGIN_EXTERNAL_AUTH_TOKEN_LIFETIME) | ||
99 | |||
100 | const user = buildUserResult(authResult) | ||
101 | authBypassTokens.set(bypassToken, { | ||
102 | expires, | ||
103 | user, | ||
104 | npmName, | ||
105 | authName | ||
106 | }) | ||
107 | |||
108 | // Cleanup | ||
109 | const now = new Date() | ||
110 | for (const [ key, value ] of authBypassTokens) { | ||
111 | if (value.expires.getTime() < now.getTime()) { | ||
112 | authBypassTokens.delete(key) | ||
113 | } | ||
114 | } | ||
115 | |||
116 | res.redirect(`/login?externalAuthToken=${bypassToken}&username=${user.username}`) | ||
117 | } | ||
118 | |||
119 | // --------------------------------------------------------------------------- | ||
120 | |||
121 | export { oAuthServer, handleLogin, onExternalUserAuthenticated, handleTokenRevocation } | ||
122 | |||
123 | // --------------------------------------------------------------------------- | ||
124 | |||
125 | function forwardTokenReq (req: express.Request, res: express.Response, next?: express.NextFunction) { | ||
126 | return oAuthServer.token()(req, res, err => { | ||
127 | if (err) { | ||
128 | logger.warn('Login error.', { err }) | ||
129 | |||
130 | return res.status(err.status) | ||
131 | .json({ | ||
132 | error: err.message, | ||
133 | code: err.name | ||
134 | }) | ||
135 | } | ||
136 | |||
137 | if (next) return next() | ||
138 | }) | ||
139 | } | ||
140 | |||
141 | async function proxifyRefreshGrant (req: express.Request, res: express.Response) { | ||
142 | const refreshToken = req.body.refresh_token | ||
143 | if (!refreshToken) return | ||
144 | |||
145 | const tokenModel = await OAuthTokenModel.loadByRefreshToken(refreshToken) | ||
146 | if (tokenModel?.authName) res.locals.refreshTokenAuthName = tokenModel.authName | ||
147 | } | ||
148 | |||
149 | async function proxifyPasswordGrant (req: express.Request, res: express.Response) { | ||
150 | const plugins = PluginManager.Instance.getIdAndPassAuths() | ||
151 | const pluginAuths: { npmName?: string, registerAuthOptions: RegisterServerAuthPassOptions }[] = [] | ||
152 | |||
153 | for (const plugin of plugins) { | ||
154 | const auths = plugin.idAndPassAuths | ||
155 | |||
156 | for (const auth of auths) { | ||
157 | pluginAuths.push({ | ||
158 | npmName: plugin.npmName, | ||
159 | registerAuthOptions: auth | ||
160 | }) | ||
161 | } | ||
162 | } | ||
163 | |||
164 | pluginAuths.sort((a, b) => { | ||
165 | const aWeight = a.registerAuthOptions.getWeight() | ||
166 | const bWeight = b.registerAuthOptions.getWeight() | ||
167 | |||
168 | // DESC weight order | ||
169 | if (aWeight === bWeight) return 0 | ||
170 | if (aWeight < bWeight) return 1 | ||
171 | return -1 | ||
172 | }) | ||
173 | |||
174 | const loginOptions = { | ||
175 | id: req.body.username, | ||
176 | password: req.body.password | ||
177 | } | ||
178 | |||
179 | for (const pluginAuth of pluginAuths) { | ||
180 | const authOptions = pluginAuth.registerAuthOptions | ||
181 | const authName = authOptions.authName | ||
182 | const npmName = pluginAuth.npmName | ||
183 | |||
184 | logger.debug( | ||
185 | 'Using auth method %s of plugin %s to login %s with weight %d.', | ||
186 | authName, npmName, loginOptions.id, authOptions.getWeight() | ||
187 | ) | ||
188 | |||
189 | try { | ||
190 | const loginResult = await authOptions.login(loginOptions) | ||
191 | |||
192 | if (!loginResult) continue | ||
193 | if (!isAuthResultValid(pluginAuth.npmName, authOptions.authName, loginResult)) continue | ||
194 | |||
195 | logger.info( | ||
196 | 'Login success with auth method %s of plugin %s for %s.', | ||
197 | authName, npmName, loginOptions.id | ||
198 | ) | ||
199 | |||
200 | res.locals.bypassLogin = { | ||
201 | bypass: true, | ||
202 | pluginName: pluginAuth.npmName, | ||
203 | authName: authOptions.authName, | ||
204 | user: buildUserResult(loginResult) | ||
205 | } | ||
206 | |||
207 | return | ||
208 | } catch (err) { | ||
209 | logger.error('Error in auth method %s of plugin %s', authOptions.authName, pluginAuth.npmName, { err }) | ||
210 | } | ||
211 | } | ||
212 | } | ||
213 | |||
214 | function proxifyExternalAuthBypass (req: express.Request, res: express.Response) { | ||
215 | const obj = authBypassTokens.get(req.body.externalAuthToken) | ||
216 | if (!obj) { | ||
217 | logger.error('Cannot authenticate user with unknown bypass token') | ||
218 | return res.sendStatus(400) | ||
219 | } | ||
220 | |||
221 | const { expires, user, authName, npmName } = obj | ||
222 | |||
223 | const now = new Date() | ||
224 | if (now.getTime() > expires.getTime()) { | ||
225 | logger.error('Cannot authenticate user with an expired external auth token') | ||
226 | return res.sendStatus(400) | ||
227 | } | ||
228 | |||
229 | if (user.username !== req.body.username) { | ||
230 | logger.error('Cannot authenticate user %s with invalid username %s.', req.body.username) | ||
231 | return res.sendStatus(400) | ||
232 | } | ||
233 | |||
234 | // Bypass oauth library validation | ||
235 | req.body.password = 'fake' | ||
236 | |||
237 | logger.info( | ||
238 | 'Auth success with external auth method %s of plugin %s for %s.', | ||
239 | authName, npmName, user.email | ||
240 | ) | ||
241 | |||
242 | res.locals.bypassLogin = { | ||
243 | bypass: true, | ||
244 | pluginName: npmName, | ||
245 | authName: authName, | ||
246 | user | ||
247 | } | ||
248 | } | ||
249 | |||
250 | function isAuthResultValid (npmName: string, authName: string, result: RegisterServerAuthenticatedResult) { | ||
251 | if (!isUserUsernameValid(result.username)) { | ||
252 | logger.error('Auth method %s of plugin %s did not provide a valid username.', authName, npmName, { username: result.username }) | ||
253 | return false | ||
254 | } | ||
255 | |||
256 | if (!result.email) { | ||
257 | logger.error('Auth method %s of plugin %s did not provide a valid email.', authName, npmName, { email: result.email }) | ||
258 | return false | ||
259 | } | ||
260 | |||
261 | // role is optional | ||
262 | if (result.role && !isUserRoleValid(result.role)) { | ||
263 | logger.error('Auth method %s of plugin %s did not provide a valid role.', authName, npmName, { role: result.role }) | ||
264 | return false | ||
265 | } | ||
266 | |||
267 | // display name is optional | ||
268 | if (result.displayName && !isUserDisplayNameValid(result.displayName)) { | ||
269 | logger.error( | ||
270 | 'Auth method %s of plugin %s did not provide a valid display name.', | ||
271 | authName, npmName, { displayName: result.displayName } | ||
272 | ) | ||
273 | return false | ||
274 | } | ||
275 | |||
276 | return true | ||
277 | } | ||
278 | |||
279 | function buildUserResult (pluginResult: RegisterServerAuthenticatedResult) { | ||
280 | return { | ||
281 | username: pluginResult.username, | ||
282 | email: pluginResult.email, | ||
283 | role: pluginResult.role ?? UserRole.USER, | ||
284 | displayName: pluginResult.displayName || pluginResult.username | ||
285 | } | ||
286 | } | ||
diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts index ad4cdd3ab..282d834a2 100644 --- a/server/lib/avatar.ts +++ b/server/lib/avatar.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import 'multer' | 1 | import 'multer' |
2 | import { sendUpdateActor } from './activitypub/send' | 2 | import { sendUpdateActor } from './activitypub/send' |
3 | import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' | 3 | import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' |
4 | import { updateActorAvatarInstance } from './activitypub' | 4 | import { updateActorAvatarInstance } from './activitypub/actor' |
5 | import { processImage } from '../helpers/image-utils' | 5 | import { processImage } from '../helpers/image-utils' |
6 | import { extname, join } from 'path' | 6 | import { extname, join } from 'path' |
7 | import { retryTransactionWrapper } from '../helpers/database-utils' | 7 | import { retryTransactionWrapper } from '../helpers/database-utils' |
8 | import * as uuidv4 from 'uuid/v4' | 8 | import { v4 as uuidv4 } from 'uuid' |
9 | import { CONFIG } from '../initializers/config' | 9 | import { CONFIG } from '../initializers/config' |
10 | import { sequelizeTypescript } from '../initializers/database' | 10 | import { sequelizeTypescript } from '../initializers/database' |
11 | import * as LRUCache from 'lru-cache' | 11 | import * as LRUCache from 'lru-cache' |
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts index 28c69b46e..842eecb5b 100644 --- a/server/lib/blocklist.ts +++ b/server/lib/blocklist.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { sequelizeTypescript } from '../initializers' | 1 | import { sequelizeTypescript } from '@server/initializers/database' |
2 | import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models' | ||
2 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 3 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
3 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | 4 | import { ServerBlocklistModel } from '../models/server/server-blocklist' |
4 | import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models' | ||
5 | 5 | ||
6 | function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { | 6 | function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { |
7 | return sequelizeTypescript.transaction(async t => { | 7 | return sequelizeTypescript.transaction(async t => { |
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 1d8a08ed0..4a4b0d12f 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -17,7 +17,7 @@ import { MAccountActor, MChannelActor, MVideo } from '../typings/models' | |||
17 | 17 | ||
18 | export class ClientHtml { | 18 | export class ClientHtml { |
19 | 19 | ||
20 | private static htmlCache: { [ path: string ]: string } = {} | 20 | private static htmlCache: { [path: string]: string } = {} |
21 | 21 | ||
22 | static invalidCache () { | 22 | static invalidCache () { |
23 | logger.info('Cleaning HTML cache.') | 23 | logger.info('Cleaning HTML cache.') |
@@ -94,7 +94,7 @@ export class ClientHtml { | |||
94 | 94 | ||
95 | private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { | 95 | private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { |
96 | const path = ClientHtml.getIndexPath(req, res, paramLang) | 96 | const path = ClientHtml.getIndexPath(req, res, paramLang) |
97 | if (ClientHtml.htmlCache[ path ]) return ClientHtml.htmlCache[ path ] | 97 | if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] |
98 | 98 | ||
99 | const buffer = await readFile(path) | 99 | const buffer = await readFile(path) |
100 | 100 | ||
@@ -104,7 +104,7 @@ export class ClientHtml { | |||
104 | html = ClientHtml.addCustomCSS(html) | 104 | html = ClientHtml.addCustomCSS(html) |
105 | html = await ClientHtml.addAsyncPluginCSS(html) | 105 | html = await ClientHtml.addAsyncPluginCSS(html) |
106 | 106 | ||
107 | ClientHtml.htmlCache[ path ] = html | 107 | ClientHtml.htmlCache[path] = html |
108 | 108 | ||
109 | return html | 109 | return html |
110 | } | 110 | } |
@@ -119,7 +119,7 @@ export class ClientHtml { | |||
119 | // Save locale in cookies | 119 | // Save locale in cookies |
120 | res.cookie('clientLanguage', lang, { | 120 | res.cookie('clientLanguage', lang, { |
121 | secure: WEBSERVER.SCHEME === 'https', | 121 | secure: WEBSERVER.SCHEME === 'https', |
122 | sameSite: true, | 122 | sameSite: 'none', |
123 | maxAge: 1000 * 3600 * 24 * 90 // 3 months | 123 | maxAge: 1000 * 3600 * 24 * 90 // 3 months |
124 | }) | 124 | }) |
125 | 125 | ||
@@ -214,21 +214,21 @@ export class ClientHtml { | |||
214 | const schemaTags = { | 214 | const schemaTags = { |
215 | '@context': 'http://schema.org', | 215 | '@context': 'http://schema.org', |
216 | '@type': 'VideoObject', | 216 | '@type': 'VideoObject', |
217 | name: videoNameEscaped, | 217 | 'name': videoNameEscaped, |
218 | description: videoDescriptionEscaped, | 218 | 'description': videoDescriptionEscaped, |
219 | thumbnailUrl: previewUrl, | 219 | 'thumbnailUrl': previewUrl, |
220 | uploadDate: video.createdAt.toISOString(), | 220 | 'uploadDate': video.createdAt.toISOString(), |
221 | duration: getActivityStreamDuration(video.duration), | 221 | 'duration': getActivityStreamDuration(video.duration), |
222 | contentUrl: videoUrl, | 222 | 'contentUrl': videoUrl, |
223 | embedUrl: embedUrl, | 223 | 'embedUrl': embedUrl, |
224 | interactionCount: video.views | 224 | 'interactionCount': video.views |
225 | } | 225 | } |
226 | 226 | ||
227 | let tagsString = '' | 227 | let tagsString = '' |
228 | 228 | ||
229 | // Opengraph | 229 | // Opengraph |
230 | Object.keys(openGraphMetaTags).forEach(tagName => { | 230 | Object.keys(openGraphMetaTags).forEach(tagName => { |
231 | const tagValue = openGraphMetaTags[ tagName ] | 231 | const tagValue = openGraphMetaTags[tagName] |
232 | 232 | ||
233 | tagsString += `<meta property="${tagName}" content="${tagValue}" />` | 233 | tagsString += `<meta property="${tagName}" content="${tagValue}" />` |
234 | }) | 234 | }) |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 7484524a4..935c9e882 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { createTransport, Transporter } from 'nodemailer' | 1 | import { createTransport, Transporter } from 'nodemailer' |
2 | import { isTestInstance } from '../helpers/core-utils' | 2 | import { isTestInstance, root } from '../helpers/core-utils' |
3 | import { bunyanLogger, logger } from '../helpers/logger' | 3 | import { bunyanLogger, logger } from '../helpers/logger' |
4 | import { CONFIG } from '../initializers/config' | 4 | import { CONFIG, isEmailEnabled } from '../initializers/config' |
5 | import { JobQueue } from './job-queue' | 5 | import { JobQueue } from './job-queue' |
6 | import { EmailPayload } from './job-queue/handlers/email' | ||
7 | import { readFileSync } from 'fs-extra' | 6 | import { readFileSync } from 'fs-extra' |
8 | import { WEBSERVER } from '../initializers/constants' | 7 | import { WEBSERVER } from '../initializers/constants' |
9 | import { | 8 | import { |
@@ -16,15 +15,13 @@ import { | |||
16 | } from '../typings/models/video' | 15 | } from '../typings/models/video' |
17 | import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models' | 16 | import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models' |
18 | import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import' | 17 | import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import' |
19 | 18 | import { EmailPayload } from '@shared/models' | |
20 | type SendEmailOptions = { | 19 | import { join } from 'path' |
21 | to: string[] | 20 | import { VideoAbuse } from '../../shared/models/videos' |
22 | subject: string | 21 | import { SendEmailOptions } from '../../shared/models/server/emailer.model' |
23 | text: string | 22 | import { merge } from 'lodash' |
24 | 23 | import { VideoChannelModel } from '@server/models/video/video-channel' | |
25 | fromDisplayName?: string | 24 | const Email = require('email-templates') |
26 | replyTo?: string | ||
27 | } | ||
28 | 25 | ||
29 | class Emailer { | 26 | class Emailer { |
30 | 27 | ||
@@ -32,41 +29,52 @@ class Emailer { | |||
32 | private initialized = false | 29 | private initialized = false |
33 | private transporter: Transporter | 30 | private transporter: Transporter |
34 | 31 | ||
35 | private constructor () {} | 32 | private constructor () { |
33 | } | ||
36 | 34 | ||
37 | init () { | 35 | init () { |
38 | // Already initialized | 36 | // Already initialized |
39 | if (this.initialized === true) return | 37 | if (this.initialized === true) return |
40 | this.initialized = true | 38 | this.initialized = true |
41 | 39 | ||
42 | if (Emailer.isEnabled()) { | 40 | if (isEmailEnabled()) { |
43 | logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT) | 41 | if (CONFIG.SMTP.TRANSPORT === 'smtp') { |
42 | logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT) | ||
44 | 43 | ||
45 | let tls | 44 | let tls |
46 | if (CONFIG.SMTP.CA_FILE) { | 45 | if (CONFIG.SMTP.CA_FILE) { |
47 | tls = { | 46 | tls = { |
48 | ca: [ readFileSync(CONFIG.SMTP.CA_FILE) ] | 47 | ca: [ readFileSync(CONFIG.SMTP.CA_FILE) ] |
48 | } | ||
49 | } | 49 | } |
50 | } | ||
51 | 50 | ||
52 | let auth | 51 | let auth |
53 | if (CONFIG.SMTP.USERNAME && CONFIG.SMTP.PASSWORD) { | 52 | if (CONFIG.SMTP.USERNAME && CONFIG.SMTP.PASSWORD) { |
54 | auth = { | 53 | auth = { |
55 | user: CONFIG.SMTP.USERNAME, | 54 | user: CONFIG.SMTP.USERNAME, |
56 | pass: CONFIG.SMTP.PASSWORD | 55 | pass: CONFIG.SMTP.PASSWORD |
56 | } | ||
57 | } | 57 | } |
58 | } | ||
59 | 58 | ||
60 | this.transporter = createTransport({ | 59 | this.transporter = createTransport({ |
61 | host: CONFIG.SMTP.HOSTNAME, | 60 | host: CONFIG.SMTP.HOSTNAME, |
62 | port: CONFIG.SMTP.PORT, | 61 | port: CONFIG.SMTP.PORT, |
63 | secure: CONFIG.SMTP.TLS, | 62 | secure: CONFIG.SMTP.TLS, |
64 | debug: CONFIG.LOG.LEVEL === 'debug', | 63 | debug: CONFIG.LOG.LEVEL === 'debug', |
65 | logger: bunyanLogger as any, | 64 | logger: bunyanLogger as any, |
66 | ignoreTLS: CONFIG.SMTP.DISABLE_STARTTLS, | 65 | ignoreTLS: CONFIG.SMTP.DISABLE_STARTTLS, |
67 | tls, | 66 | tls, |
68 | auth | 67 | auth |
69 | }) | 68 | }) |
69 | } else { // sendmail | ||
70 | logger.info('Using sendmail to send emails') | ||
71 | |||
72 | this.transporter = createTransport({ | ||
73 | sendmail: true, | ||
74 | newline: 'unix', | ||
75 | path: CONFIG.SMTP.SENDMAIL | ||
76 | }) | ||
77 | } | ||
70 | } else { | 78 | } else { |
71 | if (!isTestInstance()) { | 79 | if (!isTestInstance()) { |
72 | logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!') | 80 | logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!') |
@@ -75,11 +83,17 @@ class Emailer { | |||
75 | } | 83 | } |
76 | 84 | ||
77 | static isEnabled () { | 85 | static isEnabled () { |
78 | return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT | 86 | if (CONFIG.SMTP.TRANSPORT === 'sendmail') { |
87 | return !!CONFIG.SMTP.SENDMAIL | ||
88 | } else if (CONFIG.SMTP.TRANSPORT === 'smtp') { | ||
89 | return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT | ||
90 | } else { | ||
91 | return false | ||
92 | } | ||
79 | } | 93 | } |
80 | 94 | ||
81 | async checkConnectionOrDie () { | 95 | async checkConnectionOrDie () { |
82 | if (!this.transporter) return | 96 | if (!this.transporter || CONFIG.SMTP.TRANSPORT !== 'smtp') return |
83 | 97 | ||
84 | logger.info('Testing SMTP server...') | 98 | logger.info('Testing SMTP server...') |
85 | 99 | ||
@@ -97,37 +111,36 @@ class Emailer { | |||
97 | const channelName = video.VideoChannel.getDisplayName() | 111 | const channelName = video.VideoChannel.getDisplayName() |
98 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 112 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
99 | 113 | ||
100 | const text = `Hi dear user,\n\n` + | ||
101 | `Your subscription ${channelName} just published a new video: ${video.name}` + | ||
102 | `\n\n` + | ||
103 | `You can view it on ${videoUrl} ` + | ||
104 | `\n\n` + | ||
105 | `Cheers,\n` + | ||
106 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
107 | |||
108 | const emailPayload: EmailPayload = { | 114 | const emailPayload: EmailPayload = { |
109 | to, | 115 | to, |
110 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + channelName + ' just published a new video', | 116 | subject: channelName + ' just published a new video', |
111 | text | 117 | text: `Your subscription ${channelName} just published a new video: "${video.name}".`, |
118 | locals: { | ||
119 | title: 'New content ', | ||
120 | action: { | ||
121 | text: 'View video', | ||
122 | url: videoUrl | ||
123 | } | ||
124 | } | ||
112 | } | 125 | } |
113 | 126 | ||
114 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 127 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
115 | } | 128 | } |
116 | 129 | ||
117 | addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') { | 130 | addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') { |
118 | const followerName = actorFollow.ActorFollower.Account.getDisplayName() | ||
119 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() | 131 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() |
120 | 132 | ||
121 | const text = `Hi dear user,\n\n` + | ||
122 | `Your ${followType} ${followingName} has a new subscriber: ${followerName}` + | ||
123 | `\n\n` + | ||
124 | `Cheers,\n` + | ||
125 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
126 | |||
127 | const emailPayload: EmailPayload = { | 133 | const emailPayload: EmailPayload = { |
134 | template: 'follower-on-channel', | ||
128 | to, | 135 | to, |
129 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New follower on your channel ' + followingName, | 136 | subject: `New follower on your channel ${followingName}`, |
130 | text | 137 | locals: { |
138 | followerName: actorFollow.ActorFollower.Account.getDisplayName(), | ||
139 | followerUrl: actorFollow.ActorFollower.url, | ||
140 | followingName, | ||
141 | followingUrl: actorFollow.ActorFollowing.url, | ||
142 | followType | ||
143 | } | ||
131 | } | 144 | } |
132 | 145 | ||
133 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 146 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -136,32 +149,28 @@ class Emailer { | |||
136 | addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { | 149 | addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { |
137 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' | 150 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' |
138 | 151 | ||
139 | const text = `Hi dear admin,\n\n` + | ||
140 | `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + | ||
141 | `\n\n` + | ||
142 | `Cheers,\n` + | ||
143 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
144 | |||
145 | const emailPayload: EmailPayload = { | 152 | const emailPayload: EmailPayload = { |
146 | to, | 153 | to, |
147 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New instance follower', | 154 | subject: 'New instance follower', |
148 | text | 155 | text: `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}.`, |
156 | locals: { | ||
157 | title: 'New instance follower', | ||
158 | action: { | ||
159 | text: 'Review followers', | ||
160 | url: WEBSERVER.URL + '/admin/follows/followers-list' | ||
161 | } | ||
162 | } | ||
149 | } | 163 | } |
150 | 164 | ||
151 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 165 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
152 | } | 166 | } |
153 | 167 | ||
154 | addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { | 168 | addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { |
155 | const text = `Hi dear admin,\n\n` + | 169 | const instanceUrl = actorFollow.ActorFollowing.url |
156 | `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + | ||
157 | `\n\n` + | ||
158 | `Cheers,\n` + | ||
159 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
160 | |||
161 | const emailPayload: EmailPayload = { | 170 | const emailPayload: EmailPayload = { |
162 | to, | 171 | to, |
163 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following', | 172 | subject: 'Auto instance following', |
164 | text | 173 | text: `Your instance automatically followed a new instance: <a href="${instanceUrl}">${instanceUrl}</a>.` |
165 | } | 174 | } |
166 | 175 | ||
167 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 176 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -170,18 +179,17 @@ class Emailer { | |||
170 | myVideoPublishedNotification (to: string[], video: MVideo) { | 179 | myVideoPublishedNotification (to: string[], video: MVideo) { |
171 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 180 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
172 | 181 | ||
173 | const text = `Hi dear user,\n\n` + | ||
174 | `Your video ${video.name} has been published.` + | ||
175 | `\n\n` + | ||
176 | `You can view it on ${videoUrl} ` + | ||
177 | `\n\n` + | ||
178 | `Cheers,\n` + | ||
179 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
180 | |||
181 | const emailPayload: EmailPayload = { | 182 | const emailPayload: EmailPayload = { |
182 | to, | 183 | to, |
183 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video ${video.name} is published`, | 184 | subject: `Your video ${video.name} has been published`, |
184 | text | 185 | text: `Your video "${video.name}" has been published.`, |
186 | locals: { | ||
187 | title: 'You video is live', | ||
188 | action: { | ||
189 | text: 'View video', | ||
190 | url: videoUrl | ||
191 | } | ||
192 | } | ||
185 | } | 193 | } |
186 | 194 | ||
187 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 195 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -190,18 +198,17 @@ class Emailer { | |||
190 | myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { | 198 | myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { |
191 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() | 199 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() |
192 | 200 | ||
193 | const text = `Hi dear user,\n\n` + | ||
194 | `Your video import ${videoImport.getTargetIdentifier()} is finished.` + | ||
195 | `\n\n` + | ||
196 | `You can view the imported video on ${videoUrl} ` + | ||
197 | `\n\n` + | ||
198 | `Cheers,\n` + | ||
199 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
200 | |||
201 | const emailPayload: EmailPayload = { | 201 | const emailPayload: EmailPayload = { |
202 | to, | 202 | to, |
203 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} is finished`, | 203 | subject: `Your video import ${videoImport.getTargetIdentifier()} is complete`, |
204 | text | 204 | text: `Your video "${videoImport.getTargetIdentifier()}" just finished importing.`, |
205 | locals: { | ||
206 | title: 'Import complete', | ||
207 | action: { | ||
208 | text: 'View video', | ||
209 | url: videoUrl | ||
210 | } | ||
211 | } | ||
205 | } | 212 | } |
206 | 213 | ||
207 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 214 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -210,40 +217,47 @@ class Emailer { | |||
210 | myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { | 217 | myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { |
211 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' | 218 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' |
212 | 219 | ||
213 | const text = `Hi dear user,\n\n` + | 220 | const text = |
214 | `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + | 221 | `Your video import "${videoImport.getTargetIdentifier()}" encountered an error.` + |
215 | `\n\n` + | 222 | '\n\n' + |
216 | `See your videos import dashboard for more information: ${importUrl}` + | 223 | `See your videos import dashboard for more information: <a href="${importUrl}">${importUrl}</a>.` |
217 | `\n\n` + | ||
218 | `Cheers,\n` + | ||
219 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
220 | 224 | ||
221 | const emailPayload: EmailPayload = { | 225 | const emailPayload: EmailPayload = { |
222 | to, | 226 | to, |
223 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Your video import ${videoImport.getTargetIdentifier()} encountered an error`, | 227 | subject: `Your video import "${videoImport.getTargetIdentifier()}" encountered an error`, |
224 | text | 228 | text, |
229 | locals: { | ||
230 | title: 'Import failed', | ||
231 | action: { | ||
232 | text: 'Review imports', | ||
233 | url: importUrl | ||
234 | } | ||
235 | } | ||
225 | } | 236 | } |
226 | 237 | ||
227 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 238 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
228 | } | 239 | } |
229 | 240 | ||
230 | addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) { | 241 | addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) { |
231 | const accountName = comment.Account.getDisplayName() | ||
232 | const video = comment.Video | 242 | const video = comment.Video |
243 | const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() | ||
233 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 244 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
234 | 245 | ||
235 | const text = `Hi dear user,\n\n` + | ||
236 | `A new comment has been posted by ${accountName} on your video ${video.name}` + | ||
237 | `\n\n` + | ||
238 | `You can view it on ${commentUrl} ` + | ||
239 | `\n\n` + | ||
240 | `Cheers,\n` + | ||
241 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
242 | |||
243 | const emailPayload: EmailPayload = { | 246 | const emailPayload: EmailPayload = { |
247 | template: 'video-comment-new', | ||
244 | to, | 248 | to, |
245 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New comment on your video ' + video.name, | 249 | subject: 'New comment on your video ' + video.name, |
246 | text | 250 | locals: { |
251 | accountName: comment.Account.getDisplayName(), | ||
252 | accountUrl: comment.Account.Actor.url, | ||
253 | comment, | ||
254 | video, | ||
255 | videoUrl, | ||
256 | action: { | ||
257 | text: 'View comment', | ||
258 | url: commentUrl | ||
259 | } | ||
260 | } | ||
247 | } | 261 | } |
248 | 262 | ||
249 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 263 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -252,75 +266,88 @@ class Emailer { | |||
252 | addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) { | 266 | addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) { |
253 | const accountName = comment.Account.getDisplayName() | 267 | const accountName = comment.Account.getDisplayName() |
254 | const video = comment.Video | 268 | const video = comment.Video |
269 | const videoUrl = WEBSERVER.URL + comment.Video.getWatchStaticPath() | ||
255 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 270 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
256 | 271 | ||
257 | const text = `Hi dear user,\n\n` + | ||
258 | `${accountName} mentioned you on video ${video.name}` + | ||
259 | `\n\n` + | ||
260 | `You can view the comment on ${commentUrl} ` + | ||
261 | `\n\n` + | ||
262 | `Cheers,\n` + | ||
263 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
264 | |||
265 | const emailPayload: EmailPayload = { | 272 | const emailPayload: EmailPayload = { |
273 | template: 'video-comment-mention', | ||
266 | to, | 274 | to, |
267 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Mention on video ' + video.name, | 275 | subject: 'Mention on video ' + video.name, |
268 | text | 276 | locals: { |
277 | comment, | ||
278 | video, | ||
279 | videoUrl, | ||
280 | accountName, | ||
281 | action: { | ||
282 | text: 'View comment', | ||
283 | url: commentUrl | ||
284 | } | ||
285 | } | ||
269 | } | 286 | } |
270 | 287 | ||
271 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 288 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
272 | } | 289 | } |
273 | 290 | ||
274 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { | 291 | addVideoAbuseModeratorsNotification (to: string[], parameters: { |
275 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() | 292 | videoAbuse: VideoAbuse |
276 | 293 | videoAbuseInstance: MVideoAbuseVideo | |
277 | const text = `Hi,\n\n` + | 294 | reporter: string |
278 | `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + | 295 | }) { |
279 | `Cheers,\n` + | 296 | const videoAbuseUrl = WEBSERVER.URL + '/admin/moderation/video-abuses/list?search=%23' + parameters.videoAbuse.id |
280 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 297 | const videoUrl = WEBSERVER.URL + parameters.videoAbuseInstance.Video.getWatchStaticPath() |
281 | 298 | ||
282 | const emailPayload: EmailPayload = { | 299 | const emailPayload: EmailPayload = { |
300 | template: 'video-abuse-new', | ||
283 | to, | 301 | to, |
284 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Received a video abuse', | 302 | subject: `New video abuse report from ${parameters.reporter}`, |
285 | text | 303 | locals: { |
304 | videoUrl, | ||
305 | videoAbuseUrl, | ||
306 | videoCreatedAt: new Date(parameters.videoAbuseInstance.Video.createdAt).toLocaleString(), | ||
307 | videoPublishedAt: new Date(parameters.videoAbuseInstance.Video.publishedAt).toLocaleString(), | ||
308 | videoAbuse: parameters.videoAbuse, | ||
309 | reporter: parameters.reporter, | ||
310 | action: { | ||
311 | text: 'View report #' + parameters.videoAbuse.id, | ||
312 | url: videoAbuseUrl | ||
313 | } | ||
314 | } | ||
286 | } | 315 | } |
287 | 316 | ||
288 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 317 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
289 | } | 318 | } |
290 | 319 | ||
291 | addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { | 320 | async addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { |
292 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' | 321 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' |
293 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() | 322 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
294 | 323 | const channel = (await VideoChannelModel.loadByIdAndPopulateAccount(videoBlacklist.Video.channelId)).toFormattedSummaryJSON() | |
295 | const text = `Hi,\n\n` + | ||
296 | `A recently added video was auto-blacklisted and requires moderator review before publishing.` + | ||
297 | `\n\n` + | ||
298 | `You can view it and take appropriate action on ${videoUrl}` + | ||
299 | `\n\n` + | ||
300 | `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + | ||
301 | `\n\n` + | ||
302 | `Cheers,\n` + | ||
303 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
304 | 324 | ||
305 | const emailPayload: EmailPayload = { | 325 | const emailPayload: EmailPayload = { |
326 | template: 'video-auto-blacklist-new', | ||
306 | to, | 327 | to, |
307 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'An auto-blacklisted video is awaiting review', | 328 | subject: 'A new video is pending moderation', |
308 | text | 329 | locals: { |
330 | channel, | ||
331 | videoUrl, | ||
332 | videoName: videoBlacklist.Video.name, | ||
333 | action: { | ||
334 | text: 'Review autoblacklist', | ||
335 | url: VIDEO_AUTO_BLACKLIST_URL | ||
336 | } | ||
337 | } | ||
309 | } | 338 | } |
310 | 339 | ||
311 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 340 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
312 | } | 341 | } |
313 | 342 | ||
314 | addNewUserRegistrationNotification (to: string[], user: MUser) { | 343 | addNewUserRegistrationNotification (to: string[], user: MUser) { |
315 | const text = `Hi,\n\n` + | ||
316 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + | ||
317 | `Cheers,\n` + | ||
318 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
319 | |||
320 | const emailPayload: EmailPayload = { | 344 | const emailPayload: EmailPayload = { |
345 | template: 'user-registered', | ||
321 | to, | 346 | to, |
322 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'New user registration on ' + WEBSERVER.HOST, | 347 | subject: `a new user registered on ${WEBSERVER.HOST}: ${user.username}`, |
323 | text | 348 | locals: { |
349 | user | ||
350 | } | ||
324 | } | 351 | } |
325 | 352 | ||
326 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 353 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -333,16 +360,13 @@ class Emailer { | |||
333 | const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : '' | 360 | const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : '' |
334 | const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.` | 361 | const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.` |
335 | 362 | ||
336 | const text = 'Hi,\n\n' + | ||
337 | blockedString + | ||
338 | '\n\n' + | ||
339 | 'Cheers,\n' + | ||
340 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
341 | |||
342 | const emailPayload: EmailPayload = { | 363 | const emailPayload: EmailPayload = { |
343 | to, | 364 | to, |
344 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${videoName} blacklisted`, | 365 | subject: `Video ${videoName} blacklisted`, |
345 | text | 366 | text: blockedString, |
367 | locals: { | ||
368 | title: 'Your video was blacklisted' | ||
369 | } | ||
346 | } | 370 | } |
347 | 371 | ||
348 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 372 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -351,50 +375,53 @@ class Emailer { | |||
351 | addVideoUnblacklistNotification (to: string[], video: MVideo) { | 375 | addVideoUnblacklistNotification (to: string[], video: MVideo) { |
352 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 376 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
353 | 377 | ||
354 | const text = 'Hi,\n\n' + | ||
355 | `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` + | ||
356 | '\n\n' + | ||
357 | 'Cheers,\n' + | ||
358 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
359 | |||
360 | const emailPayload: EmailPayload = { | 378 | const emailPayload: EmailPayload = { |
361 | to, | 379 | to, |
362 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + `Video ${video.name} unblacklisted`, | 380 | subject: `Video ${video.name} unblacklisted`, |
363 | text | 381 | text: `Your video "${video.name}" (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.`, |
382 | locals: { | ||
383 | title: 'Your video was unblacklisted' | ||
384 | } | ||
364 | } | 385 | } |
365 | 386 | ||
366 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 387 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
367 | } | 388 | } |
368 | 389 | ||
369 | addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { | 390 | addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { |
370 | const text = `Hi dear user,\n\n` + | 391 | const emailPayload: EmailPayload = { |
371 | `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` + | 392 | template: 'password-reset', |
372 | `Please follow this link to reset it: ${resetPasswordUrl} (the link will expire within 1 hour)\n\n` + | 393 | to: [ to ], |
373 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 394 | subject: 'Reset your account password', |
374 | `Cheers,\n` + | 395 | locals: { |
375 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | 396 | resetPasswordUrl |
397 | } | ||
398 | } | ||
376 | 399 | ||
400 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
401 | } | ||
402 | |||
403 | addPasswordCreateEmailJob (username: string, to: string, createPasswordUrl: string) { | ||
377 | const emailPayload: EmailPayload = { | 404 | const emailPayload: EmailPayload = { |
405 | template: 'password-create', | ||
378 | to: [ to ], | 406 | to: [ to ], |
379 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Reset your password', | 407 | subject: 'Create your account password', |
380 | text | 408 | locals: { |
409 | username, | ||
410 | createPasswordUrl | ||
411 | } | ||
381 | } | 412 | } |
382 | 413 | ||
383 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 414 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
384 | } | 415 | } |
385 | 416 | ||
386 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { | 417 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { |
387 | const text = `Welcome to PeerTube,\n\n` + | ||
388 | `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` + | ||
389 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + | ||
390 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | ||
391 | `Cheers,\n` + | ||
392 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
393 | |||
394 | const emailPayload: EmailPayload = { | 418 | const emailPayload: EmailPayload = { |
419 | template: 'verify-email', | ||
395 | to: [ to ], | 420 | to: [ to ], |
396 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Verify your email', | 421 | subject: `Verify your email on ${WEBSERVER.HOST}`, |
397 | text | 422 | locals: { |
423 | verifyEmailUrl | ||
424 | } | ||
398 | } | 425 | } |
399 | 426 | ||
400 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 427 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
@@ -403,61 +430,76 @@ class Emailer { | |||
403 | addUserBlockJob (user: MUser, blocked: boolean, reason?: string) { | 430 | addUserBlockJob (user: MUser, blocked: boolean, reason?: string) { |
404 | const reasonString = reason ? ` for the following reason: ${reason}` : '' | 431 | const reasonString = reason ? ` for the following reason: ${reason}` : '' |
405 | const blockedWord = blocked ? 'blocked' : 'unblocked' | 432 | const blockedWord = blocked ? 'blocked' : 'unblocked' |
406 | const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` | ||
407 | |||
408 | const text = 'Hi,\n\n' + | ||
409 | blockedString + | ||
410 | '\n\n' + | ||
411 | 'Cheers,\n' + | ||
412 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
413 | 433 | ||
414 | const to = user.email | 434 | const to = user.email |
415 | const emailPayload: EmailPayload = { | 435 | const emailPayload: EmailPayload = { |
416 | to: [ to ], | 436 | to: [ to ], |
417 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Account ' + blockedWord, | 437 | subject: 'Account ' + blockedWord, |
418 | text | 438 | text: `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` |
419 | } | 439 | } |
420 | 440 | ||
421 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 441 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
422 | } | 442 | } |
423 | 443 | ||
424 | addContactFormJob (fromEmail: string, fromName: string, subject: string, body: string) { | 444 | addContactFormJob (fromEmail: string, fromName: string, subject: string, body: string) { |
425 | const text = 'Hello dear admin,\n\n' + | ||
426 | fromName + ' sent you a message' + | ||
427 | '\n\n---------------------------------------\n\n' + | ||
428 | body + | ||
429 | '\n\n---------------------------------------\n\n' + | ||
430 | 'Cheers,\n' + | ||
431 | 'PeerTube.' | ||
432 | |||
433 | const emailPayload: EmailPayload = { | 445 | const emailPayload: EmailPayload = { |
434 | fromDisplayName: fromEmail, | 446 | template: 'contact-form', |
435 | replyTo: fromEmail, | ||
436 | to: [ CONFIG.ADMIN.EMAIL ], | 447 | to: [ CONFIG.ADMIN.EMAIL ], |
437 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + subject, | 448 | replyTo: `"${fromName}" <${fromEmail}>`, |
438 | text | 449 | subject: `(contact form) ${subject}`, |
450 | locals: { | ||
451 | fromName, | ||
452 | fromEmail, | ||
453 | body | ||
454 | } | ||
439 | } | 455 | } |
440 | 456 | ||
441 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 457 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
442 | } | 458 | } |
443 | 459 | ||
444 | async sendMail (options: EmailPayload) { | 460 | async sendMail (options: EmailPayload) { |
445 | if (!Emailer.isEnabled()) { | 461 | if (!isEmailEnabled()) { |
446 | throw new Error('Cannot send mail because SMTP is not configured.') | 462 | throw new Error('Cannot send mail because SMTP is not configured.') |
447 | } | 463 | } |
448 | 464 | ||
449 | const fromDisplayName = options.fromDisplayName | 465 | const fromDisplayName = options.from |
450 | ? options.fromDisplayName | 466 | ? options.from |
451 | : WEBSERVER.HOST | 467 | : WEBSERVER.HOST |
452 | 468 | ||
469 | const email = new Email({ | ||
470 | send: true, | ||
471 | message: { | ||
472 | from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>` | ||
473 | }, | ||
474 | transport: this.transporter, | ||
475 | views: { | ||
476 | root: join(root(), 'server', 'lib', 'emails') | ||
477 | }, | ||
478 | subjectPrefix: CONFIG.EMAIL.SUBJECT.PREFIX | ||
479 | }) | ||
480 | |||
453 | for (const to of options.to) { | 481 | for (const to of options.to) { |
454 | await this.transporter.sendMail({ | 482 | await email |
455 | from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`, | 483 | .send(merge( |
456 | replyTo: options.replyTo, | 484 | { |
457 | to, | 485 | template: 'common', |
458 | subject: options.subject, | 486 | message: { |
459 | text: options.text | 487 | to, |
460 | }) | 488 | from: options.from, |
489 | subject: options.subject, | ||
490 | replyTo: options.replyTo | ||
491 | }, | ||
492 | locals: { // default variables available in all templates | ||
493 | WEBSERVER, | ||
494 | EMAIL: CONFIG.EMAIL, | ||
495 | text: options.text, | ||
496 | subject: options.subject | ||
497 | } | ||
498 | }, | ||
499 | options // overriden/new variables given for a specific template in the payload | ||
500 | ) as SendEmailOptions) | ||
501 | .then(logger.info) | ||
502 | .catch(logger.error) | ||
461 | } | 503 | } |
462 | } | 504 | } |
463 | 505 | ||
@@ -474,6 +516,5 @@ class Emailer { | |||
474 | // --------------------------------------------------------------------------- | 516 | // --------------------------------------------------------------------------- |
475 | 517 | ||
476 | export { | 518 | export { |
477 | Emailer, | 519 | Emailer |
478 | SendEmailOptions | ||
479 | } | 520 | } |
diff --git a/server/lib/emails/common/base.pug b/server/lib/emails/common/base.pug new file mode 100644 index 000000000..9a1894cab --- /dev/null +++ b/server/lib/emails/common/base.pug | |||
@@ -0,0 +1,267 @@ | |||
1 | //- | ||
2 | The email background color is defined in three places: | ||
3 | 1. body tag: for most email clients | ||
4 | 2. center tag: for Gmail and Inbox mobile apps and web versions of Gmail, GSuite, Inbox, Yahoo, AOL, Libero, Comcast, freenet, Mail.ru, Orange.fr | ||
5 | 3. mso conditional: For Windows 10 Mail | ||
6 | - var backgroundColor = "#fff"; | ||
7 | - var mainColor = "#f2690d"; | ||
8 | doctype html | ||
9 | head | ||
10 | // This template is heavily adapted from the Cerberus Fluid template. Kudos to them! | ||
11 | meta(charset='utf-8') | ||
12 | //- utf-8 works for most cases | ||
13 | meta(name='viewport' content='width=device-width') | ||
14 | //- Forcing initial-scale shouldn't be necessary | ||
15 | meta(http-equiv='X-UA-Compatible' content='IE=edge') | ||
16 | //- Use the latest (edge) version of IE rendering engine | ||
17 | meta(name='x-apple-disable-message-reformatting') | ||
18 | //- Disable auto-scale in iOS 10 Mail entirely | ||
19 | meta(name='format-detection' content='telephone=no,address=no,email=no,date=no,url=no') | ||
20 | //- Tell iOS not to automatically link certain text strings. | ||
21 | meta(name='color-scheme' content='light') | ||
22 | meta(name='supported-color-schemes' content='light') | ||
23 | //- The title tag shows in email notifications, like Android 4.4. | ||
24 | title #{subject} | ||
25 | //- What it does: Makes background images in 72ppi Outlook render at correct size. | ||
26 | //if gte mso 9 | ||
27 | xml | ||
28 | o:officedocumentsettings | ||
29 | o:allowpng | ||
30 | o:pixelsperinch 96 | ||
31 | //- CSS Reset : BEGIN | ||
32 | style. | ||
33 | /* What it does: Tells the email client that only light styles are provided but the client can transform them to dark. A duplicate of meta color-scheme meta tag above. */ | ||
34 | :root { | ||
35 | color-scheme: light; | ||
36 | supported-color-schemes: light; | ||
37 | } | ||
38 | /* What it does: Remove spaces around the email design added by some email clients. */ | ||
39 | /* Beware: It can remove the padding / margin and add a background color to the compose a reply window. */ | ||
40 | html, | ||
41 | body { | ||
42 | margin: 0 auto !important; | ||
43 | padding: 0 !important; | ||
44 | height: 100% !important; | ||
45 | width: 100% !important; | ||
46 | } | ||
47 | /* What it does: Stops email clients resizing small text. */ | ||
48 | * { | ||
49 | -ms-text-size-adjust: 100%; | ||
50 | -webkit-text-size-adjust: 100%; | ||
51 | } | ||
52 | /* What it does: Centers email on Android 4.4 */ | ||
53 | div[style*="margin: 16px 0"] { | ||
54 | margin: 0 !important; | ||
55 | } | ||
56 | /* What it does: forces Samsung Android mail clients to use the entire viewport */ | ||
57 | #MessageViewBody, #MessageWebViewDiv{ | ||
58 | width: 100% !important; | ||
59 | } | ||
60 | /* What it does: Stops Outlook from adding extra spacing to tables. */ | ||
61 | table, | ||
62 | td { | ||
63 | mso-table-lspace: 0pt !important; | ||
64 | mso-table-rspace: 0pt !important; | ||
65 | } | ||
66 | /* What it does: Fixes webkit padding issue. */ | ||
67 | table { | ||
68 | border-spacing: 0 !important; | ||
69 | border-collapse: collapse !important; | ||
70 | table-layout: fixed !important; | ||
71 | margin: 0 auto !important; | ||
72 | } | ||
73 | /* What it does: Uses a better rendering method when resizing images in IE. */ | ||
74 | img { | ||
75 | -ms-interpolation-mode:bicubic; | ||
76 | } | ||
77 | /* What it does: Prevents Windows 10 Mail from underlining links despite inline CSS. Styles for underlined links should be inline. */ | ||
78 | a { | ||
79 | text-decoration: none; | ||
80 | } | ||
81 | a:not(.nocolor) { | ||
82 | color: #{mainColor}; | ||
83 | } | ||
84 | a.nocolor { | ||
85 | color: inherit !important; | ||
86 | } | ||
87 | /* What it does: A work-around for email clients meddling in triggered links. */ | ||
88 | a[x-apple-data-detectors], /* iOS */ | ||
89 | .unstyle-auto-detected-links a, | ||
90 | .aBn { | ||
91 | border-bottom: 0 !important; | ||
92 | cursor: default !important; | ||
93 | color: inherit !important; | ||
94 | text-decoration: none !important; | ||
95 | font-size: inherit !important; | ||
96 | font-family: inherit !important; | ||
97 | font-weight: inherit !important; | ||
98 | line-height: inherit !important; | ||
99 | } | ||
100 | /* What it does: Prevents Gmail from displaying a download button on large, non-linked images. */ | ||
101 | .a6S { | ||
102 | display: none !important; | ||
103 | opacity: 0.01 !important; | ||
104 | } | ||
105 | /* What it does: Prevents Gmail from changing the text color in conversation threads. */ | ||
106 | .im { | ||
107 | color: inherit !important; | ||
108 | } | ||
109 | /* If the above doesn't work, add a .g-img class to any image in question. */ | ||
110 | img.g-img + div { | ||
111 | display: none !important; | ||
112 | } | ||
113 | /* What it does: Removes right gutter in Gmail iOS app: https://github.com/TedGoas/Cerberus/issues/89 */ | ||
114 | /* Create one of these media queries for each additional viewport size you'd like to fix */ | ||
115 | /* iPhone 4, 4S, 5, 5S, 5C, and 5SE */ | ||
116 | @media only screen and (min-device-width: 320px) and (max-device-width: 374px) { | ||
117 | u ~ div .email-container { | ||
118 | min-width: 320px !important; | ||
119 | } | ||
120 | } | ||
121 | /* iPhone 6, 6S, 7, 8, and X */ | ||
122 | @media only screen and (min-device-width: 375px) and (max-device-width: 413px) { | ||
123 | u ~ div .email-container { | ||
124 | min-width: 375px !important; | ||
125 | } | ||
126 | } | ||
127 | /* iPhone 6+, 7+, and 8+ */ | ||
128 | @media only screen and (min-device-width: 414px) { | ||
129 | u ~ div .email-container { | ||
130 | min-width: 414px !important; | ||
131 | } | ||
132 | } | ||
133 | //- CSS Reset : END | ||
134 | //- CSS for PeerTube : START | ||
135 | style. | ||
136 | blockquote { | ||
137 | margin-left: 0; | ||
138 | padding-left: 20px; | ||
139 | border-left: 2px solid #f2690d; | ||
140 | } | ||
141 | //- CSS for PeerTube : END | ||
142 | //- Progressive Enhancements : BEGIN | ||
143 | style. | ||
144 | /* What it does: Hover styles for buttons */ | ||
145 | .button-td, | ||
146 | .button-a { | ||
147 | transition: all 100ms ease-in; | ||
148 | } | ||
149 | .button-td-primary:hover, | ||
150 | .button-a-primary:hover { | ||
151 | background: #555555 !important; | ||
152 | border-color: #555555 !important; | ||
153 | } | ||
154 | /* Media Queries */ | ||
155 | @media screen and (max-width: 600px) { | ||
156 | /* What it does: Adjust typography on small screens to improve readability */ | ||
157 | .email-container p { | ||
158 | font-size: 17px !important; | ||
159 | } | ||
160 | } | ||
161 | //- Progressive Enhancements : END | ||
162 | |||
163 | body(width="100%" style="margin: 0; padding: 0 !important; mso-line-height-rule: exactly; background-color: #{backgroundColor};") | ||
164 | center(role='article' aria-roledescription='email' lang='en' style='width: 100%; background-color: #{backgroundColor};') | ||
165 | //if mso | IE | ||
166 | table(role='presentation' border='0' cellpadding='0' cellspacing='0' width='100%' style='background-color: #fff;') | ||
167 | tr | ||
168 | td | ||
169 | //- Visually Hidden Preheader Text : BEGIN | ||
170 | div(style='max-height:0; overflow:hidden; mso-hide:all;' aria-hidden='true') | ||
171 | block preheader | ||
172 | //- Visually Hidden Preheader Text : END | ||
173 | |||
174 | //- Create white space after the desired preview text so email clients don’t pull other distracting text into the inbox preview. Extend as necessary. | ||
175 | //- Preview Text Spacing Hack : BEGIN | ||
176 | div(style='display: none; font-size: 1px; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden; mso-hide: all; font-family: sans-serif;') | ||
177 | | ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ ‌ | ||
178 | //- Preview Text Spacing Hack : END | ||
179 | |||
180 | //- | ||
181 | Set the email width. Defined in two places: | ||
182 | 1. max-width for all clients except Desktop Windows Outlook, allowing the email to squish on narrow but never go wider than 600px. | ||
183 | 2. MSO tags for Desktop Windows Outlook enforce a 600px width. | ||
184 | .email-container(style='max-width: 600px; margin: 0 auto;') | ||
185 | //if mso | ||
186 | table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='600') | ||
187 | tr | ||
188 | td | ||
189 | //- Email Body : BEGIN | ||
190 | table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style='margin: auto;') | ||
191 | //- 1 Column Text + Button : BEGIN | ||
192 | tr | ||
193 | td(style='background-color: #ffffff;') | ||
194 | table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%') | ||
195 | tr | ||
196 | td(style='padding: 20px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;') | ||
197 | table(role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%") | ||
198 | tr | ||
199 | td(width="40px") | ||
200 | img(src=`${WEBSERVER.URL}/client/assets/images/icons/icon-192x192.png` width="auto" height="30px" alt="icon" border="0" style="height: 30px; background: #ffffff; font-family: sans-serif; font-size: 15px; line-height: 15px; color: #555555;") | ||
201 | td | ||
202 | h1(style='margin: 10px 0 10px 0; font-family: sans-serif; font-size: 25px; line-height: 30px; color: #333333; font-weight: normal;') | ||
203 | block title | ||
204 | if title | ||
205 | | #{title} | ||
206 | else | ||
207 | | Something requires your attention | ||
208 | p(style='margin: 0;') | ||
209 | block body | ||
210 | if action | ||
211 | tr | ||
212 | td(style='padding: 0 20px;') | ||
213 | //- Button : BEGIN | ||
214 | table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' style='margin: auto;') | ||
215 | tr | ||
216 | td.button-td.button-td-primary(style='border-radius: 4px; background: #222222;') | ||
217 | a.button-a.button-a-primary(href=action.url style='background: #222222; border: 1px solid #000000; font-family: sans-serif; font-size: 15px; line-height: 15px; text-decoration: none; padding: 13px 17px; color: #ffffff; display: block; border-radius: 4px;') #{action.text} | ||
218 | //- Button : END | ||
219 | //- 1 Column Text + Button : END | ||
220 | //- Clear Spacer : BEGIN | ||
221 | tr | ||
222 | td(aria-hidden='true' height='20' style='font-size: 0px; line-height: 0px;') | ||
223 | br | ||
224 | //- Clear Spacer : END | ||
225 | //- 1 Column Text : BEGIN | ||
226 | if username | ||
227 | tr | ||
228 | td(style='background-color: #cccccc;') | ||
229 | table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%') | ||
230 | tr | ||
231 | td(style='padding: 20px; font-family: sans-serif; font-size: 15px; line-height: 20px; color: #555555;') | ||
232 | p(style='margin: 0;') | ||
233 | | You are receiving this email as part of your notification settings on #{WEBSERVER.HOST} for your account #{username}. | ||
234 | //- 1 Column Text : END | ||
235 | //- Email Body : END | ||
236 | //- Email Footer : BEGIN | ||
237 | table(align='center' role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style='margin: auto;') | ||
238 | tr | ||
239 | td(style='padding: 20px; padding-bottom: 0px; font-family: sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;') | ||
240 | webversion | ||
241 | a.nocolor(href=`${WEBSERVER.URL}/my-account/notifications` style='color: #cccccc; font-weight: bold;') View in your notifications | ||
242 | br | ||
243 | tr | ||
244 | td(style='padding: 20px; padding-top: 10px; font-family: sans-serif; font-size: 12px; line-height: 15px; text-align: center; color: #888888;') | ||
245 | unsubscribe | ||
246 | a.nocolor(href=`${WEBSERVER.URL}/my-account/settings#notifications` style='color: #888888;') Manage your notification preferences in your profile | ||
247 | br | ||
248 | //- Email Footer : END | ||
249 | //if mso | ||
250 | //- Full Bleed Background Section : BEGIN | ||
251 | table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%' style=`background-color: ${mainColor};`) | ||
252 | tr | ||
253 | td | ||
254 | .email-container(align='center' style='max-width: 600px; margin: auto;') | ||
255 | //if mso | ||
256 | table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='600' align='center') | ||
257 | tr | ||
258 | td | ||
259 | table(role='presentation' cellspacing='0' cellpadding='0' border='0' width='100%') | ||
260 | tr | ||
261 | td(style='padding: 20px; text-align: left; font-family: sans-serif; font-size: 12px; line-height: 20px; color: #ffffff;') | ||
262 | table(role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%") | ||
263 | tr | ||
264 | td(valign="top") #[a(href="https://github.com/Chocobozzz/PeerTube" style="color: white !important") PeerTube © 2015-#{new Date().getFullYear()}] #[a(href="https://github.com/Chocobozzz/PeerTube/blob/master/CREDITS.md" style="color: white !important") PeerTube Contributors] | ||
265 | //if mso | ||
266 | //- Full Bleed Background Section : END | ||
267 | //if mso | IE | ||
diff --git a/server/lib/emails/common/greetings.pug b/server/lib/emails/common/greetings.pug new file mode 100644 index 000000000..5efe29dfb --- /dev/null +++ b/server/lib/emails/common/greetings.pug | |||
@@ -0,0 +1,11 @@ | |||
1 | extends base | ||
2 | |||
3 | block body | ||
4 | if username | ||
5 | p Hi #{username}, | ||
6 | else | ||
7 | p Hi, | ||
8 | block content | ||
9 | p | ||
10 | | Cheers,#[br] | ||
11 | | #{EMAIL.BODY.SIGNATURE} \ No newline at end of file | ||
diff --git a/server/lib/emails/common/html.pug b/server/lib/emails/common/html.pug new file mode 100644 index 000000000..d76168b85 --- /dev/null +++ b/server/lib/emails/common/html.pug | |||
@@ -0,0 +1,4 @@ | |||
1 | extends greetings | ||
2 | |||
3 | block content | ||
4 | p !{text} \ No newline at end of file | ||
diff --git a/server/lib/emails/common/mixins.pug b/server/lib/emails/common/mixins.pug new file mode 100644 index 000000000..76b805a24 --- /dev/null +++ b/server/lib/emails/common/mixins.pug | |||
@@ -0,0 +1,3 @@ | |||
1 | mixin channel(channel) | ||
2 | - var handle = `${channel.name}@${channel.host}` | ||
3 | | #[a(href=`${WEBSERVER.URL}/video-channels/${handle}` title=handle) #{channel.displayName}] \ No newline at end of file | ||
diff --git a/server/lib/emails/contact-form/html.pug b/server/lib/emails/contact-form/html.pug new file mode 100644 index 000000000..0073ff78e --- /dev/null +++ b/server/lib/emails/contact-form/html.pug | |||
@@ -0,0 +1,9 @@ | |||
1 | extends ../common/greetings | ||
2 | |||
3 | block title | ||
4 | | Someone just used the contact form | ||
5 | |||
6 | block content | ||
7 | p #{fromName} sent you a message via the contact form on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}]: | ||
8 | blockquote(style='white-space: pre-wrap') #{body} | ||
9 | p You can contact them at #[a(href=`mailto:${fromEmail}`) #{fromEmail}], or simply reply to this email to get in touch. \ No newline at end of file | ||
diff --git a/server/lib/emails/follower-on-channel/html.pug b/server/lib/emails/follower-on-channel/html.pug new file mode 100644 index 000000000..8a352e90f --- /dev/null +++ b/server/lib/emails/follower-on-channel/html.pug | |||
@@ -0,0 +1,9 @@ | |||
1 | extends ../common/greetings | ||
2 | |||
3 | block title | ||
4 | | New follower on your channel | ||
5 | |||
6 | block content | ||
7 | p. | ||
8 | Your #{followType} #[a(href=followingUrl) #{followingName}] has a new subscriber: | ||
9 | #[a(href=followerUrl) #{followerName}]. \ No newline at end of file | ||
diff --git a/server/lib/emails/password-create/html.pug b/server/lib/emails/password-create/html.pug new file mode 100644 index 000000000..45ff3078a --- /dev/null +++ b/server/lib/emails/password-create/html.pug | |||
@@ -0,0 +1,10 @@ | |||
1 | extends ../common/greetings | ||
2 | |||
3 | block title | ||
4 | | Password creation for your account | ||
5 | |||
6 | block content | ||
7 | p. | ||
8 | Welcome to #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}], your PeerTube instance. Your username is: #{username}. | ||
9 | Please set your password by following #[a(href=createPasswordUrl) this link]: #[a(href=createPasswordUrl) #{createPasswordUrl}] | ||
10 | (this link will expire within seven days). \ No newline at end of file | ||
diff --git a/server/lib/emails/password-reset/html.pug b/server/lib/emails/password-reset/html.pug new file mode 100644 index 000000000..bb6a9d16b --- /dev/null +++ b/server/lib/emails/password-reset/html.pug | |||
@@ -0,0 +1,12 @@ | |||
1 | extends ../common/greetings | ||
2 | |||
3 | block title | ||
4 | | Password reset for your account | ||
5 | |||
6 | block content | ||
7 | p. | ||
8 | A reset password procedure for your account ${to} has been requested on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}]. | ||
9 | Please follow #[a(href=resetPasswordUrl) this link] to reset it: #[a(href=resetPasswordUrl) #{resetPasswordUrl}] | ||
10 | (the link will expire within 1 hour) | ||
11 | p. | ||
12 | If you are not the person who initiated this request, please ignore this email. \ No newline at end of file | ||
diff --git a/server/lib/emails/user-registered/html.pug b/server/lib/emails/user-registered/html.pug new file mode 100644 index 000000000..20f62125e --- /dev/null +++ b/server/lib/emails/user-registered/html.pug | |||
@@ -0,0 +1,10 @@ | |||
1 | extends ../common/greetings | ||
2 | |||
3 | block title | ||
4 | | A new user registered | ||
5 | |||
6 | block content | ||
7 | - var mail = user.email || user.pendingEmail; | ||
8 | p | ||
9 | | User #[a(href=`${WEBSERVER.URL}/accounts/${user.username}`) #{user.username}] just registered. | ||
10 | | You might want to contact them at #[a(href=`mailto:${mail}`) #{mail}]. \ No newline at end of file | ||
diff --git a/server/lib/emails/verify-email/html.pug b/server/lib/emails/verify-email/html.pug new file mode 100644 index 000000000..8a4a77703 --- /dev/null +++ b/server/lib/emails/verify-email/html.pug | |||
@@ -0,0 +1,14 @@ | |||
1 | extends ../common/greetings | ||
2 | |||
3 | block title | ||
4 | | Account verification | ||
5 | |||
6 | block content | ||
7 | p Welcome to PeerTube! | ||
8 | p. | ||
9 | You just created an account #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}], your new PeerTube instance. | ||
10 | Your username there is: #{username}. | ||
11 | p. | ||
12 | To start using PeerTube on #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] you must verify your email first! | ||
13 | Please follow #[a(href=verifyEmailUrl) this link] to verify this email belongs to you: #[a(href=verifyEmailUrl) #{verifyEmailUrl}] | ||
14 | If you are not the person who initiated this request, please ignore this email. \ No newline at end of file | ||
diff --git a/server/lib/emails/video-abuse-new/html.pug b/server/lib/emails/video-abuse-new/html.pug new file mode 100644 index 000000000..999c89d26 --- /dev/null +++ b/server/lib/emails/video-abuse-new/html.pug | |||
@@ -0,0 +1,18 @@ | |||
1 | extends ../common/greetings | ||
2 | include ../common/mixins.pug | ||
3 | |||
4 | block title | ||
5 | | A video is pending moderation | ||
6 | |||
7 | block content | ||
8 | p | ||
9 | | #[a(href=WEBSERVER.URL) #{WEBSERVER.HOST}] received an abuse report for the #{videoAbuse.video.channel.isLocal ? '' : 'remote '}video " | ||
10 | a(href=videoUrl) #{videoAbuse.video.name} | ||
11 | | " by #[+channel(videoAbuse.video.channel)] | ||
12 | if videoPublishedAt | ||
13 | | , published the #{videoPublishedAt}. | ||
14 | else | ||
15 | | , uploaded the #{videoCreatedAt} but not yet published. | ||
16 | p The reporter, #{reporter}, cited the following reason(s): | ||
17 | blockquote #{videoAbuse.reason} | ||
18 | br(style="display: none;") | ||
diff --git a/server/lib/emails/video-auto-blacklist-new/html.pug b/server/lib/emails/video-auto-blacklist-new/html.pug new file mode 100644 index 000000000..07c8dfd16 --- /dev/null +++ b/server/lib/emails/video-auto-blacklist-new/html.pug | |||
@@ -0,0 +1,17 @@ | |||
1 | extends ../common/greetings | ||
2 | include ../common/mixins | ||
3 | |||
4 | block title | ||
5 | | A video is pending moderation | ||
6 | |||
7 | block content | ||
8 | p | ||
9 | | A recently added video was auto-blacklisted and requires moderator review before going public: | ||
10 | | | ||
11 | a(href=videoUrl) #{videoName} | ||
12 | | | ||
13 | | by #[+channel(channel)]. | ||
14 | p. | ||
15 | Apart from the publisher and the moderation team, no one will be able to see the video until you | ||
16 | unblacklist it. If you trust the publisher, any admin can whitelist the user for later videos so | ||
17 | that they don't require approval before going public. | ||
diff --git a/server/lib/emails/video-comment-mention/html.pug b/server/lib/emails/video-comment-mention/html.pug new file mode 100644 index 000000000..9e9ced62d --- /dev/null +++ b/server/lib/emails/video-comment-mention/html.pug | |||
@@ -0,0 +1,11 @@ | |||
1 | extends ../common/greetings | ||
2 | |||
3 | block title | ||
4 | | Someone mentioned you | ||
5 | |||
6 | block content | ||
7 | p. | ||
8 | #[a(href=accountUrl title=handle) #{accountName}] mentioned you in a comment on video | ||
9 | "#[a(href=videoUrl) #{video.name}]": | ||
10 | blockquote #{comment.text} | ||
11 | br(style="display: none;") \ No newline at end of file | ||
diff --git a/server/lib/emails/video-comment-new/html.pug b/server/lib/emails/video-comment-new/html.pug new file mode 100644 index 000000000..075af5717 --- /dev/null +++ b/server/lib/emails/video-comment-new/html.pug | |||
@@ -0,0 +1,11 @@ | |||
1 | extends ../common/greetings | ||
2 | |||
3 | block title | ||
4 | | Someone commented your video | ||
5 | |||
6 | block content | ||
7 | p. | ||
8 | #[a(href=accountUrl title=handle) #{accountName}] added a comment on your video | ||
9 | "#[a(href=videoUrl) #{video.name}]": | ||
10 | blockquote #{comment.text} | ||
11 | br(style="display: none;") \ No newline at end of file | ||
diff --git a/server/lib/files-cache/videos-caption-cache.ts b/server/lib/files-cache/videos-caption-cache.ts index 440c3fde8..26ab3bd0d 100644 --- a/server/lib/files-cache/videos-caption-cache.ts +++ b/server/lib/files-cache/videos-caption-cache.ts | |||
@@ -5,7 +5,7 @@ import { VideoCaptionModel } from '../../models/video/video-caption' | |||
5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
6 | import { CONFIG } from '../../initializers/config' | 6 | import { CONFIG } from '../../initializers/config' |
7 | import { logger } from '../../helpers/logger' | 7 | import { logger } from '../../helpers/logger' |
8 | import { fetchRemoteVideoStaticFile } from '../activitypub' | 8 | import { doRequestAndSaveToFile } from '@server/helpers/requests' |
9 | 9 | ||
10 | type GetPathParam = { videoId: string, language: string } | 10 | type GetPathParam = { videoId: string, language: string } |
11 | 11 | ||
@@ -46,11 +46,10 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | |||
46 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 46 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) |
47 | if (!video) return undefined | 47 | if (!video) return undefined |
48 | 48 | ||
49 | // FIXME: use URL | 49 | const remoteUrl = videoCaption.getFileUrl(video) |
50 | const remoteStaticPath = videoCaption.getCaptionStaticPath() | ||
51 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) | 50 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) |
52 | 51 | ||
53 | await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) | 52 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) |
54 | 53 | ||
55 | return { isOwned: false, path: destPath } | 54 | return { isOwned: false, path: destPath } |
56 | } | 55 | } |
diff --git a/server/lib/files-cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts index 3da6bb138..d0d4fc5b5 100644 --- a/server/lib/files-cache/videos-preview-cache.ts +++ b/server/lib/files-cache/videos-preview-cache.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { FILES_CACHE, STATIC_PATHS } from '../../initializers/constants' | 2 | import { FILES_CACHE } from '../../initializers/constants' |
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
5 | import { CONFIG } from '../../initializers/config' | 5 | import { doRequestAndSaveToFile } from '@server/helpers/requests' |
6 | import { fetchRemoteVideoStaticFile } from '../activitypub' | ||
7 | 6 | ||
8 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | 7 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { |
9 | 8 | ||
@@ -32,11 +31,11 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
32 | 31 | ||
33 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') | 32 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') |
34 | 33 | ||
35 | // FIXME: use URL | 34 | const preview = video.getPreview() |
36 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename) | 35 | const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, preview.filename) |
37 | const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename) | ||
38 | 36 | ||
39 | await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) | 37 | const remoteUrl = preview.getFileUrl(video) |
38 | await doRequestAndSaveToFile({ uri: remoteUrl }, destPath) | ||
40 | 39 | ||
41 | return { isOwned: false, path: destPath } | 40 | return { isOwned: false, path: destPath } |
42 | } | 41 | } |
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts index 4a7cda0a2..7034c10d0 100644 --- a/server/lib/job-queue/handlers/activitypub-follow.ts +++ b/server/lib/job-queue/handlers/activitypub-follow.ts | |||
@@ -11,13 +11,7 @@ import { ActorModel } from '../../../models/activitypub/actor' | |||
11 | import { Notifier } from '../../notifier' | 11 | import { Notifier } from '../../notifier' |
12 | import { sequelizeTypescript } from '../../../initializers/database' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
13 | import { MActor, MActorFollowActors, MActorFull } from '../../../typings/models' | 13 | import { MActor, MActorFollowActors, MActorFull } from '../../../typings/models' |
14 | 14 | import { ActivitypubFollowPayload } from '@shared/models' | |
15 | export type ActivitypubFollowPayload = { | ||
16 | followerActorId: number | ||
17 | name: string | ||
18 | host: string | ||
19 | isAutoFollow?: boolean | ||
20 | } | ||
21 | 15 | ||
22 | async function processActivityPubFollow (job: Bull.Job) { | 16 | async function processActivityPubFollow (job: Bull.Job) { |
23 | const payload = job.data as ActivitypubFollowPayload | 17 | const payload = job.data as ActivitypubFollowPayload |
@@ -34,6 +28,11 @@ async function processActivityPubFollow (job: Bull.Job) { | |||
34 | targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all') | 28 | targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all') |
35 | } | 29 | } |
36 | 30 | ||
31 | if (payload.assertIsChannel && !targetActor.VideoChannel) { | ||
32 | logger.warn('Do not follow %s@%s because it is not a channel.', payload.name, host) | ||
33 | return | ||
34 | } | ||
35 | |||
37 | const fromActor = await ActorModel.load(payload.followerActorId) | 36 | const fromActor = await ActorModel.load(payload.followerActorId) |
38 | 37 | ||
39 | return retryTransactionWrapper(follow, fromActor, targetActor, payload.isAutoFollow) | 38 | return retryTransactionWrapper(follow, fromActor, targetActor, payload.isAutoFollow) |
diff --git a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts index 0ff7b44a0..e4d3dbbff 100644 --- a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts | |||
@@ -5,12 +5,7 @@ import { doRequest } from '../../../helpers/requests' | |||
5 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' | 5 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' |
6 | import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' | 6 | import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' |
7 | import { ActorFollowScoreCache } from '../../files-cache' | 7 | import { ActorFollowScoreCache } from '../../files-cache' |
8 | 8 | import { ActivitypubHttpBroadcastPayload } from '@shared/models' | |
9 | export type ActivitypubHttpBroadcastPayload = { | ||
10 | uris: string[] | ||
11 | signatureActorId?: number | ||
12 | body: any | ||
13 | } | ||
14 | 9 | ||
15 | async function processActivityPubHttpBroadcast (job: Bull.Job) { | 10 | async function processActivityPubHttpBroadcast (job: Bull.Job) { |
16 | logger.info('Processing ActivityPub broadcast in job %d.', job.id) | 11 | logger.info('Processing ActivityPub broadcast in job %d.', job.id) |
diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts index 0182c5169..524aadc27 100644 --- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts +++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts | |||
@@ -5,22 +5,15 @@ import { processActivities } from '../../activitypub/process' | |||
5 | import { addVideoComments } from '../../activitypub/video-comments' | 5 | import { addVideoComments } from '../../activitypub/video-comments' |
6 | import { crawlCollectionPage } from '../../activitypub/crawl' | 6 | import { crawlCollectionPage } from '../../activitypub/crawl' |
7 | import { VideoModel } from '../../../models/video/video' | 7 | import { VideoModel } from '../../../models/video/video' |
8 | import { addVideoShares, createRates } from '../../activitypub' | 8 | import { addVideoShares } from '../../activitypub/share' |
9 | import { createRates } from '../../activitypub/video-rates' | ||
9 | import { createAccountPlaylists } from '../../activitypub/playlist' | 10 | import { createAccountPlaylists } from '../../activitypub/playlist' |
10 | import { AccountModel } from '../../../models/account/account' | 11 | import { AccountModel } from '../../../models/account/account' |
11 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 12 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
12 | import { VideoShareModel } from '../../../models/video/video-share' | 13 | import { VideoShareModel } from '../../../models/video/video-share' |
13 | import { VideoCommentModel } from '../../../models/video/video-comment' | 14 | import { VideoCommentModel } from '../../../models/video/video-comment' |
14 | import { MAccountDefault, MVideoFullLight } from '../../../typings/models' | 15 | import { MAccountDefault, MVideoFullLight } from '../../../typings/models' |
15 | 16 | import { ActivitypubHttpFetcherPayload, FetchType } from '@shared/models' | |
16 | type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' | ||
17 | |||
18 | export type ActivitypubHttpFetcherPayload = { | ||
19 | uri: string | ||
20 | type: FetchType | ||
21 | videoId?: number | ||
22 | accountId?: number | ||
23 | } | ||
24 | 17 | ||
25 | async function processActivityPubHttpFetcher (job: Bull.Job) { | 18 | async function processActivityPubHttpFetcher (job: Bull.Job) { |
26 | logger.info('Processing ActivityPub fetcher in job %d.', job.id) | 19 | logger.info('Processing ActivityPub fetcher in job %d.', job.id) |
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts index c70ce3be9..b65eeb677 100644 --- a/server/lib/job-queue/handlers/activitypub-http-unicast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts | |||
@@ -4,12 +4,7 @@ import { doRequest } from '../../../helpers/requests' | |||
4 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' | 4 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' |
5 | import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' | 5 | import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' |
6 | import { ActorFollowScoreCache } from '../../files-cache' | 6 | import { ActorFollowScoreCache } from '../../files-cache' |
7 | 7 | import { ActivitypubHttpUnicastPayload } from '@shared/models' | |
8 | export type ActivitypubHttpUnicastPayload = { | ||
9 | uri: string | ||
10 | signatureActorId?: number | ||
11 | body: any | ||
12 | } | ||
13 | 8 | ||
14 | async function processActivityPubHttpUnicast (job: Bull.Job) { | 9 | async function processActivityPubHttpUnicast (job: Bull.Job) { |
15 | logger.info('Processing ActivityPub unicast in job %d.', job.id) | 10 | logger.info('Processing ActivityPub unicast in job %d.', job.id) |
diff --git a/server/lib/job-queue/handlers/activitypub-refresher.ts b/server/lib/job-queue/handlers/activitypub-refresher.ts index 4d6c38cfa..666e56868 100644 --- a/server/lib/job-queue/handlers/activitypub-refresher.ts +++ b/server/lib/job-queue/handlers/activitypub-refresher.ts | |||
@@ -1,14 +1,12 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { fetchVideoByUrl } from '../../../helpers/video' | 3 | import { fetchVideoByUrl } from '../../../helpers/video' |
4 | import { refreshActorIfNeeded, refreshVideoIfNeeded, refreshVideoPlaylistIfNeeded } from '../../activitypub' | 4 | import { refreshActorIfNeeded } from '../../activitypub/actor' |
5 | import { refreshVideoIfNeeded } from '../../activitypub/videos' | ||
5 | import { ActorModel } from '../../../models/activitypub/actor' | 6 | import { ActorModel } from '../../../models/activitypub/actor' |
6 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | 7 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' |
7 | 8 | import { RefreshPayload } from '@shared/models' | |
8 | export type RefreshPayload = { | 9 | import { refreshVideoPlaylistIfNeeded } from '@server/lib/activitypub/playlist' |
9 | type: 'video' | 'video-playlist' | 'actor' | ||
10 | url: string | ||
11 | } | ||
12 | 10 | ||
13 | async function refreshAPObject (job: Bull.Job) { | 11 | async function refreshAPObject (job: Bull.Job) { |
14 | const payload = job.data as RefreshPayload | 12 | const payload = job.data as RefreshPayload |
diff --git a/server/lib/job-queue/handlers/email.ts b/server/lib/job-queue/handlers/email.ts index 62701222c..3157731e2 100644 --- a/server/lib/job-queue/handlers/email.ts +++ b/server/lib/job-queue/handlers/email.ts | |||
@@ -1,8 +1,7 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { Emailer, SendEmailOptions } from '../../emailer' | 3 | import { Emailer } from '../../emailer' |
4 | 4 | import { EmailPayload } from '@shared/models' | |
5 | export type EmailPayload = SendEmailOptions | ||
6 | 5 | ||
7 | async function processEmail (job: Bull.Job) { | 6 | async function processEmail (job: Bull.Job) { |
8 | const payload = job.data as EmailPayload | 7 | const payload = job.data as EmailPayload |
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts index d3bde6e6a..bcb49a731 100644 --- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts +++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | import { buildSignedActivity } from '../../../../helpers/activitypub' | 1 | import { buildSignedActivity } from '../../../../helpers/activitypub' |
2 | import { getServerActor } from '../../../../helpers/utils' | ||
3 | import { ActorModel } from '../../../../models/activitypub/actor' | 2 | import { ActorModel } from '../../../../models/activitypub/actor' |
4 | import { sha256 } from '../../../../helpers/core-utils' | 3 | import { ACTIVITY_PUB, HTTP_SIGNATURE } from '../../../../initializers/constants' |
5 | import { HTTP_SIGNATURE } from '../../../../initializers/constants' | ||
6 | import { MActor } from '../../../../typings/models' | 4 | import { MActor } from '../../../../typings/models' |
5 | import { getServerActor } from '@server/models/application/application' | ||
6 | import { buildDigest } from '@server/helpers/peertube-crypto' | ||
7 | import { ContextType } from '@shared/models/activitypub/context' | ||
7 | 8 | ||
8 | type Payload = { body: any, signatureActorId?: number } | 9 | type Payload = { body: any, contextType?: ContextType, signatureActorId?: number } |
9 | 10 | ||
10 | async function computeBody (payload: Payload) { | 11 | async function computeBody (payload: Payload) { |
11 | let body = payload.body | 12 | let body = payload.body |
@@ -13,7 +14,7 @@ async function computeBody (payload: Payload) { | |||
13 | if (payload.signatureActorId) { | 14 | if (payload.signatureActorId) { |
14 | const actorSignature = await ActorModel.load(payload.signatureActorId) | 15 | const actorSignature = await ActorModel.load(payload.signatureActorId) |
15 | if (!actorSignature) throw new Error('Unknown signature actor id.') | 16 | if (!actorSignature) throw new Error('Unknown signature actor id.') |
16 | body = await buildSignedActivity(actorSignature, payload.body) | 17 | body = await buildSignedActivity(actorSignature, payload.body, payload.contextType) |
17 | } | 18 | } |
18 | 19 | ||
19 | return body | 20 | return body |
@@ -42,18 +43,13 @@ async function buildSignedRequestOptions (payload: Payload) { | |||
42 | 43 | ||
43 | function buildGlobalHeaders (body: any) { | 44 | function buildGlobalHeaders (body: any) { |
44 | return { | 45 | return { |
45 | 'Digest': buildDigest(body) | 46 | 'Digest': buildDigest(body), |
47 | 'Content-Type': 'application/activity+json', | ||
48 | 'Accept': ACTIVITY_PUB.ACCEPT_HEADER | ||
46 | } | 49 | } |
47 | } | 50 | } |
48 | 51 | ||
49 | function buildDigest (body: any) { | ||
50 | const rawBody = typeof body === 'string' ? body : JSON.stringify(body) | ||
51 | |||
52 | return 'SHA-256=' + sha256(rawBody, 'base64') | ||
53 | } | ||
54 | |||
55 | export { | 52 | export { |
56 | buildDigest, | ||
57 | buildGlobalHeaders, | 53 | buildGlobalHeaders, |
58 | computeBody, | 54 | computeBody, |
59 | buildSignedRequestOptions | 55 | buildSignedRequestOptions |
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 99c991e72..ae11f1de3 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts | |||
@@ -9,11 +9,7 @@ import { extname } from 'path' | |||
9 | import { MVideoFile, MVideoWithFile } from '@server/typings/models' | 9 | import { MVideoFile, MVideoWithFile } from '@server/typings/models' |
10 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' | 10 | import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' |
11 | import { getVideoFilePath } from '@server/lib/video-paths' | 11 | import { getVideoFilePath } from '@server/lib/video-paths' |
12 | 12 | import { VideoFileImportPayload } from '@shared/models' | |
13 | export type VideoFileImportPayload = { | ||
14 | videoUUID: string, | ||
15 | filePath: string | ||
16 | } | ||
17 | 13 | ||
18 | async function processVideoFileImport (job: Bull.Job) { | 14 | async function processVideoFileImport (job: Bull.Job) { |
19 | const payload = job.data as VideoFileImportPayload | 15 | const payload = job.data as VideoFileImportPayload |
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 1fca17584..ad549c6fc 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -7,9 +7,8 @@ import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } fro | |||
7 | import { extname } from 'path' | 7 | import { extname } from 'path' |
8 | import { VideoFileModel } from '../../../models/video/video-file' | 8 | import { VideoFileModel } from '../../../models/video/video-file' |
9 | import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants' | 9 | import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants' |
10 | import { VideoState } from '../../../../shared' | 10 | import { VideoImportPayload, VideoImportTorrentPayload, VideoImportYoutubeDLPayload, VideoState } from '../../../../shared' |
11 | import { JobQueue } from '../index' | 11 | import { federateVideoIfNeeded } from '../../activitypub/videos' |
12 | import { federateVideoIfNeeded } from '../../activitypub' | ||
13 | import { VideoModel } from '../../../models/video/video' | 12 | import { VideoModel } from '../../../models/video/video' |
14 | import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' | 13 | import { createTorrentAndSetInfoHash, downloadWebTorrentVideo } from '../../../helpers/webtorrent' |
15 | import { getSecureTorrentName } from '../../../helpers/utils' | 14 | import { getSecureTorrentName } from '../../../helpers/utils' |
@@ -17,27 +16,12 @@ import { move, remove, stat } from 'fs-extra' | |||
17 | import { Notifier } from '../../notifier' | 16 | import { Notifier } from '../../notifier' |
18 | import { CONFIG } from '../../../initializers/config' | 17 | import { CONFIG } from '../../../initializers/config' |
19 | import { sequelizeTypescript } from '../../../initializers/database' | 18 | import { sequelizeTypescript } from '../../../initializers/database' |
20 | import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' | 19 | import { generateVideoMiniature } from '../../thumbnail' |
21 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 20 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
22 | import { MThumbnail } from '../../../typings/models/video/thumbnail' | 21 | import { MThumbnail } from '../../../typings/models/video/thumbnail' |
23 | import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import' | 22 | import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import' |
24 | import { getVideoFilePath } from '@server/lib/video-paths' | 23 | import { getVideoFilePath } from '@server/lib/video-paths' |
25 | 24 | import { addOptimizeOrMergeAudioJob } from '@server/helpers/video' | |
26 | type VideoImportYoutubeDLPayload = { | ||
27 | type: 'youtube-dl' | ||
28 | videoImportId: number | ||
29 | |||
30 | thumbnailUrl: string | ||
31 | downloadThumbnail: boolean | ||
32 | downloadPreview: boolean | ||
33 | } | ||
34 | |||
35 | type VideoImportTorrentPayload = { | ||
36 | type: 'magnet-uri' | 'torrent-file' | ||
37 | videoImportId: number | ||
38 | } | ||
39 | |||
40 | export type VideoImportPayload = VideoImportYoutubeDLPayload | VideoImportTorrentPayload | ||
41 | 25 | ||
42 | async function processVideoImport (job: Bull.Job) { | 26 | async function processVideoImport (job: Bull.Job) { |
43 | const payload = job.data as VideoImportPayload | 27 | const payload = job.data as VideoImportPayload |
@@ -62,9 +46,6 @@ async function processTorrentImport (job: Bull.Job, payload: VideoImportTorrentP | |||
62 | const options = { | 46 | const options = { |
63 | videoImportId: payload.videoImportId, | 47 | videoImportId: payload.videoImportId, |
64 | 48 | ||
65 | downloadThumbnail: false, | ||
66 | downloadPreview: false, | ||
67 | |||
68 | generateThumbnail: true, | 49 | generateThumbnail: true, |
69 | generatePreview: true | 50 | generatePreview: true |
70 | } | 51 | } |
@@ -82,15 +63,11 @@ async function processYoutubeDLImport (job: Bull.Job, payload: VideoImportYoutub | |||
82 | const options = { | 63 | const options = { |
83 | videoImportId: videoImport.id, | 64 | videoImportId: videoImport.id, |
84 | 65 | ||
85 | downloadThumbnail: payload.downloadThumbnail, | 66 | generateThumbnail: payload.generateThumbnail, |
86 | downloadPreview: payload.downloadPreview, | 67 | generatePreview: payload.generatePreview |
87 | thumbnailUrl: payload.thumbnailUrl, | ||
88 | |||
89 | generateThumbnail: false, | ||
90 | generatePreview: false | ||
91 | } | 68 | } |
92 | 69 | ||
93 | return processFile(() => downloadYoutubeDLVideo(videoImport.targetUrl, VIDEO_IMPORT_TIMEOUT), videoImport, options) | 70 | return processFile(() => downloadYoutubeDLVideo(videoImport.targetUrl, payload.fileExt, VIDEO_IMPORT_TIMEOUT), videoImport, options) |
94 | } | 71 | } |
95 | 72 | ||
96 | async function getVideoImportOrDie (videoImportId: number) { | 73 | async function getVideoImportOrDie (videoImportId: number) { |
@@ -105,10 +82,6 @@ async function getVideoImportOrDie (videoImportId: number) { | |||
105 | type ProcessFileOptions = { | 82 | type ProcessFileOptions = { |
106 | videoImportId: number | 83 | videoImportId: number |
107 | 84 | ||
108 | downloadThumbnail: boolean | ||
109 | downloadPreview: boolean | ||
110 | thumbnailUrl?: string | ||
111 | |||
112 | generateThumbnail: boolean | 85 | generateThumbnail: boolean |
113 | generatePreview: boolean | 86 | generatePreview: boolean |
114 | } | 87 | } |
@@ -153,17 +126,13 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid | |||
153 | 126 | ||
154 | // Process thumbnail | 127 | // Process thumbnail |
155 | let thumbnailModel: MThumbnail | 128 | let thumbnailModel: MThumbnail |
156 | if (options.downloadThumbnail && options.thumbnailUrl) { | 129 | if (options.generateThumbnail) { |
157 | thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.MINIATURE) | ||
158 | } else if (options.generateThumbnail || options.downloadThumbnail) { | ||
159 | thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE) | 130 | thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE) |
160 | } | 131 | } |
161 | 132 | ||
162 | // Process preview | 133 | // Process preview |
163 | let previewModel: MThumbnail | 134 | let previewModel: MThumbnail |
164 | if (options.downloadPreview && options.thumbnailUrl) { | 135 | if (options.generatePreview) { |
165 | previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.PREVIEW) | ||
166 | } else if (options.generatePreview || options.downloadPreview) { | ||
167 | previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW) | 136 | previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW) |
168 | } | 137 | } |
169 | 138 | ||
@@ -214,14 +183,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: MVid | |||
214 | 183 | ||
215 | // Create transcoding jobs? | 184 | // Create transcoding jobs? |
216 | if (video.state === VideoState.TO_TRANSCODE) { | 185 | if (video.state === VideoState.TO_TRANSCODE) { |
217 | // Put uuid because we don't have id auto incremented for now | 186 | await addOptimizeOrMergeAudioJob(videoImportUpdated.Video, videoFile) |
218 | const dataInput = { | ||
219 | type: 'optimize' as 'optimize', | ||
220 | videoUUID: videoImportUpdated.Video.uuid, | ||
221 | isNewVideo: true | ||
222 | } | ||
223 | |||
224 | await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) | ||
225 | } | 187 | } |
226 | 188 | ||
227 | } catch (err) { | 189 | } catch (err) { |
diff --git a/server/lib/job-queue/handlers/video-redundancy.ts b/server/lib/job-queue/handlers/video-redundancy.ts new file mode 100644 index 000000000..6296dab05 --- /dev/null +++ b/server/lib/job-queue/handlers/video-redundancy.ts | |||
@@ -0,0 +1,17 @@ | |||
1 | import * as Bull from 'bull' | ||
2 | import { logger } from '../../../helpers/logger' | ||
3 | import { VideosRedundancyScheduler } from '@server/lib/schedulers/videos-redundancy-scheduler' | ||
4 | import { VideoRedundancyPayload } from '@shared/models' | ||
5 | |||
6 | async function processVideoRedundancy (job: Bull.Job) { | ||
7 | const payload = job.data as VideoRedundancyPayload | ||
8 | logger.info('Processing video redundancy in job %d.', job.id) | ||
9 | |||
10 | return VideosRedundancyScheduler.Instance.createManualRedundancy(payload.videoId) | ||
11 | } | ||
12 | |||
13 | // --------------------------------------------------------------------------- | ||
14 | |||
15 | export { | ||
16 | processVideoRedundancy | ||
17 | } | ||
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 39b9fac98..46d52e1cf 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -1,48 +1,22 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { VideoResolution } from '../../../../shared' | 2 | import { |
3 | MergeAudioTranscodingPayload, | ||
4 | NewResolutionTranscodingPayload, | ||
5 | OptimizeTranscodingPayload, | ||
6 | VideoTranscodingPayload | ||
7 | } from '../../../../shared' | ||
3 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
4 | import { VideoModel } from '../../../models/video/video' | 9 | import { VideoModel } from '../../../models/video/video' |
5 | import { JobQueue } from '../job-queue' | 10 | import { JobQueue } from '../job-queue' |
6 | import { federateVideoIfNeeded } from '../../activitypub' | 11 | import { federateVideoIfNeeded } from '../../activitypub/videos' |
7 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 12 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
8 | import { sequelizeTypescript } from '../../../initializers' | 13 | import { sequelizeTypescript } from '../../../initializers/database' |
9 | import * as Bluebird from 'bluebird' | ||
10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' | 14 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' |
11 | import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' | 15 | import { generateHlsPlaylist, mergeAudioVideofile, optimizeOriginalVideofile, transcodeNewResolution } from '../../video-transcoding' |
12 | import { Notifier } from '../../notifier' | 16 | import { Notifier } from '../../notifier' |
13 | import { CONFIG } from '../../../initializers/config' | 17 | import { CONFIG } from '../../../initializers/config' |
14 | import { MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/typings/models' | 18 | import { MVideoFullLight, MVideoUUID, MVideoWithFile } from '@server/typings/models' |
15 | 19 | ||
16 | interface BaseTranscodingPayload { | ||
17 | videoUUID: string | ||
18 | isNewVideo?: boolean | ||
19 | } | ||
20 | |||
21 | interface HLSTranscodingPayload extends BaseTranscodingPayload { | ||
22 | type: 'hls' | ||
23 | isPortraitMode?: boolean | ||
24 | resolution: VideoResolution | ||
25 | copyCodecs: boolean | ||
26 | } | ||
27 | |||
28 | interface NewResolutionTranscodingPayload extends BaseTranscodingPayload { | ||
29 | type: 'new-resolution' | ||
30 | isPortraitMode?: boolean | ||
31 | resolution: VideoResolution | ||
32 | } | ||
33 | |||
34 | interface MergeAudioTranscodingPayload extends BaseTranscodingPayload { | ||
35 | type: 'merge-audio' | ||
36 | resolution: VideoResolution | ||
37 | } | ||
38 | |||
39 | interface OptimizeTranscodingPayload extends BaseTranscodingPayload { | ||
40 | type: 'optimize' | ||
41 | } | ||
42 | |||
43 | export type VideoTranscodingPayload = HLSTranscodingPayload | NewResolutionTranscodingPayload | ||
44 | | OptimizeTranscodingPayload | MergeAudioTranscodingPayload | ||
45 | |||
46 | async function processVideoTranscoding (job: Bull.Job) { | 20 | async function processVideoTranscoding (job: Bull.Job) { |
47 | const payload = job.data as VideoTranscodingPayload | 21 | const payload = job.data as VideoTranscodingPayload |
48 | logger.info('Processing video file in job %d.', job.id) | 22 | logger.info('Processing video file in job %d.', job.id) |
@@ -105,7 +79,7 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O | |||
105 | 79 | ||
106 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { | 80 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { |
107 | // Maybe the video changed in database, refresh it | 81 | // Maybe the video changed in database, refresh it |
108 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) | 82 | const videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoArg.uuid, t) |
109 | // Video does not exist anymore | 83 | // Video does not exist anymore |
110 | if (!videoDatabase) return undefined | 84 | if (!videoDatabase) return undefined |
111 | 85 | ||
@@ -118,12 +92,11 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O | |||
118 | 92 | ||
119 | let videoPublished = false | 93 | let videoPublished = false |
120 | 94 | ||
95 | // Generate HLS version of the max quality file | ||
121 | const hlsPayload = Object.assign({}, payload, { resolution: videoDatabase.getMaxQualityFile().resolution }) | 96 | const hlsPayload = Object.assign({}, payload, { resolution: videoDatabase.getMaxQualityFile().resolution }) |
122 | await createHlsJobIfEnabled(hlsPayload) | 97 | await createHlsJobIfEnabled(hlsPayload) |
123 | 98 | ||
124 | if (resolutionsEnabled.length !== 0) { | 99 | if (resolutionsEnabled.length !== 0) { |
125 | const tasks: (Bluebird<Bull.Job<any>> | Promise<Bull.Job<any>>)[] = [] | ||
126 | |||
127 | for (const resolution of resolutionsEnabled) { | 100 | for (const resolution of resolutionsEnabled) { |
128 | let dataInput: VideoTranscodingPayload | 101 | let dataInput: VideoTranscodingPayload |
129 | 102 | ||
@@ -143,12 +116,9 @@ async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: O | |||
143 | } | 116 | } |
144 | } | 117 | } |
145 | 118 | ||
146 | const p = JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) | 119 | JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) |
147 | tasks.push(p) | ||
148 | } | 120 | } |
149 | 121 | ||
150 | await Promise.all(tasks) | ||
151 | |||
152 | logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) | 122 | logger.info('Transcoding jobs created for uuid %s.', videoDatabase.uuid, { resolutionsEnabled }) |
153 | } else { | 123 | } else { |
154 | // No transcoding to do, it's now published | 124 | // No transcoding to do, it's now published |
diff --git a/server/lib/job-queue/handlers/video-views.ts b/server/lib/job-queue/handlers/video-views.ts index 73fa5ed04..7211df237 100644 --- a/server/lib/job-queue/handlers/video-views.ts +++ b/server/lib/job-queue/handlers/video-views.ts | |||
@@ -3,7 +3,7 @@ import { logger } from '../../../helpers/logger' | |||
3 | import { VideoModel } from '../../../models/video/video' | 3 | import { VideoModel } from '../../../models/video/video' |
4 | import { VideoViewModel } from '../../../models/video/video-views' | 4 | import { VideoViewModel } from '../../../models/video/video-views' |
5 | import { isTestInstance } from '../../../helpers/core-utils' | 5 | import { isTestInstance } from '../../../helpers/core-utils' |
6 | import { federateVideoIfNeeded } from '../../activitypub' | 6 | import { federateVideoIfNeeded } from '../../activitypub/videos' |
7 | 7 | ||
8 | async function processVideosViews () { | 8 | async function processVideosViews () { |
9 | const lastHour = new Date() | 9 | const lastHour = new Date() |
@@ -23,6 +23,8 @@ async function processVideosViews () { | |||
23 | for (const videoId of videoIds) { | 23 | for (const videoId of videoIds) { |
24 | try { | 24 | try { |
25 | const views = await Redis.Instance.getVideoViews(videoId, hour) | 25 | const views = await Redis.Instance.getVideoViews(videoId, hour) |
26 | await Redis.Instance.deleteVideoViews(videoId, hour) | ||
27 | |||
26 | if (views) { | 28 | if (views) { |
27 | logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour) | 29 | logger.debug('Adding %d views to video %d in hour %d.', views, videoId, hour) |
28 | 30 | ||
@@ -52,8 +54,6 @@ async function processVideosViews () { | |||
52 | logger.error('Cannot create video views for video %d in hour %d.', videoId, hour, { err }) | 54 | logger.error('Cannot create video views for video %d in hour %d.', videoId, hour, { err }) |
53 | } | 55 | } |
54 | } | 56 | } |
55 | |||
56 | await Redis.Instance.deleteVideoViews(videoId, hour) | ||
57 | } catch (err) { | 57 | } catch (err) { |
58 | logger.error('Cannot update video views of video %d in hour %d.', videoId, hour, { err }) | 58 | logger.error('Cannot update video views of video %d in hour %d.', videoId, hour, { err }) |
59 | } | 59 | } |
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts index ec601e9ea..14e181835 100644 --- a/server/lib/job-queue/job-queue.ts +++ b/server/lib/job-queue/job-queue.ts | |||
@@ -1,18 +1,32 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { JobState, JobType } from '../../../shared/models' | 2 | import { |
3 | ActivitypubFollowPayload, | ||
4 | ActivitypubHttpBroadcastPayload, | ||
5 | ActivitypubHttpFetcherPayload, | ||
6 | ActivitypubHttpUnicastPayload, | ||
7 | EmailPayload, | ||
8 | JobState, | ||
9 | JobType, | ||
10 | RefreshPayload, | ||
11 | VideoFileImportPayload, | ||
12 | VideoImportPayload, | ||
13 | VideoRedundancyPayload, | ||
14 | VideoTranscodingPayload | ||
15 | } from '../../../shared/models' | ||
3 | import { logger } from '../../helpers/logger' | 16 | import { logger } from '../../helpers/logger' |
4 | import { Redis } from '../redis' | 17 | import { Redis } from '../redis' |
5 | import { JOB_ATTEMPTS, JOB_COMPLETED_LIFETIME, JOB_CONCURRENCY, JOB_TTL, REPEAT_JOBS, WEBSERVER } from '../../initializers/constants' | 18 | import { JOB_ATTEMPTS, JOB_COMPLETED_LIFETIME, JOB_CONCURRENCY, JOB_TTL, REPEAT_JOBS, WEBSERVER } from '../../initializers/constants' |
6 | import { ActivitypubHttpBroadcastPayload, processActivityPubHttpBroadcast } from './handlers/activitypub-http-broadcast' | 19 | import { processActivityPubHttpBroadcast } from './handlers/activitypub-http-broadcast' |
7 | import { ActivitypubHttpFetcherPayload, processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' | 20 | import { processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' |
8 | import { ActivitypubHttpUnicastPayload, processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' | 21 | import { processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' |
9 | import { EmailPayload, processEmail } from './handlers/email' | 22 | import { processEmail } from './handlers/email' |
10 | import { processVideoTranscoding, VideoTranscodingPayload } from './handlers/video-transcoding' | 23 | import { processVideoTranscoding } from './handlers/video-transcoding' |
11 | import { ActivitypubFollowPayload, processActivityPubFollow } from './handlers/activitypub-follow' | 24 | import { processActivityPubFollow } from './handlers/activitypub-follow' |
12 | import { processVideoImport, VideoImportPayload } from './handlers/video-import' | 25 | import { processVideoImport } from './handlers/video-import' |
13 | import { processVideosViews } from './handlers/video-views' | 26 | import { processVideosViews } from './handlers/video-views' |
14 | import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher' | 27 | import { refreshAPObject } from './handlers/activitypub-refresher' |
15 | import { processVideoFileImport, VideoFileImportPayload } from './handlers/video-file-import' | 28 | import { processVideoFileImport } from './handlers/video-file-import' |
29 | import { processVideoRedundancy } from '@server/lib/job-queue/handlers/video-redundancy' | ||
16 | 30 | ||
17 | type CreateJobArgument = | 31 | type CreateJobArgument = |
18 | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | | 32 | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | |
@@ -24,20 +38,21 @@ type CreateJobArgument = | |||
24 | { type: 'email', payload: EmailPayload } | | 38 | { type: 'email', payload: EmailPayload } | |
25 | { type: 'video-import', payload: VideoImportPayload } | | 39 | { type: 'video-import', payload: VideoImportPayload } | |
26 | { type: 'activitypub-refresher', payload: RefreshPayload } | | 40 | { type: 'activitypub-refresher', payload: RefreshPayload } | |
27 | { type: 'videos-views', payload: {} } | 41 | { type: 'videos-views', payload: {} } | |
42 | { type: 'video-redundancy', payload: VideoRedundancyPayload } | ||
28 | 43 | ||
29 | const handlers: { [ id in (JobType | 'video-file') ]: (job: Bull.Job) => Promise<any>} = { | 44 | const handlers: { [id in JobType]: (job: Bull.Job) => Promise<any> } = { |
30 | 'activitypub-http-broadcast': processActivityPubHttpBroadcast, | 45 | 'activitypub-http-broadcast': processActivityPubHttpBroadcast, |
31 | 'activitypub-http-unicast': processActivityPubHttpUnicast, | 46 | 'activitypub-http-unicast': processActivityPubHttpUnicast, |
32 | 'activitypub-http-fetcher': processActivityPubHttpFetcher, | 47 | 'activitypub-http-fetcher': processActivityPubHttpFetcher, |
33 | 'activitypub-follow': processActivityPubFollow, | 48 | 'activitypub-follow': processActivityPubFollow, |
34 | 'video-file-import': processVideoFileImport, | 49 | 'video-file-import': processVideoFileImport, |
35 | 'video-transcoding': processVideoTranscoding, | 50 | 'video-transcoding': processVideoTranscoding, |
36 | 'video-file': processVideoTranscoding, // TODO: remove it (changed in 1.3) | ||
37 | 'email': processEmail, | 51 | 'email': processEmail, |
38 | 'video-import': processVideoImport, | 52 | 'video-import': processVideoImport, |
39 | 'videos-views': processVideosViews, | 53 | 'videos-views': processVideosViews, |
40 | 'activitypub-refresher': refreshAPObject | 54 | 'activitypub-refresher': refreshAPObject, |
55 | 'video-redundancy': processVideoRedundancy | ||
41 | } | 56 | } |
42 | 57 | ||
43 | const jobTypes: JobType[] = [ | 58 | const jobTypes: JobType[] = [ |
@@ -50,20 +65,22 @@ const jobTypes: JobType[] = [ | |||
50 | 'video-file-import', | 65 | 'video-file-import', |
51 | 'video-import', | 66 | 'video-import', |
52 | 'videos-views', | 67 | 'videos-views', |
53 | 'activitypub-refresher' | 68 | 'activitypub-refresher', |
69 | 'video-redundancy' | ||
54 | ] | 70 | ] |
55 | 71 | ||
56 | class JobQueue { | 72 | class JobQueue { |
57 | 73 | ||
58 | private static instance: JobQueue | 74 | private static instance: JobQueue |
59 | 75 | ||
60 | private queues: { [ id in JobType ]?: Bull.Queue } = {} | 76 | private queues: { [id in JobType]?: Bull.Queue } = {} |
61 | private initialized = false | 77 | private initialized = false |
62 | private jobRedisPrefix: string | 78 | private jobRedisPrefix: string |
63 | 79 | ||
64 | private constructor () {} | 80 | private constructor () { |
81 | } | ||
65 | 82 | ||
66 | async init () { | 83 | init () { |
67 | // Already initialized | 84 | // Already initialized |
68 | if (this.initialized === true) return | 85 | if (this.initialized === true) return |
69 | this.initialized = true | 86 | this.initialized = true |
@@ -105,11 +122,16 @@ class JobQueue { | |||
105 | } | 122 | } |
106 | } | 123 | } |
107 | 124 | ||
108 | createJob (obj: CreateJobArgument) { | 125 | createJob (obj: CreateJobArgument): void { |
126 | this.createJobWithPromise(obj) | ||
127 | .catch(err => logger.error('Cannot create job.', { err, obj })) | ||
128 | } | ||
129 | |||
130 | createJobWithPromise (obj: CreateJobArgument) { | ||
109 | const queue = this.queues[obj.type] | 131 | const queue = this.queues[obj.type] |
110 | if (queue === undefined) { | 132 | if (queue === undefined) { |
111 | logger.error('Unknown queue %s: cannot create job.', obj.type) | 133 | logger.error('Unknown queue %s: cannot create job.', obj.type) |
112 | throw Error('Unknown queue, cannot create job') | 134 | return |
113 | } | 135 | } |
114 | 136 | ||
115 | const jobArgs: Bull.JobOptions = { | 137 | const jobArgs: Bull.JobOptions = { |
@@ -122,10 +144,10 @@ class JobQueue { | |||
122 | } | 144 | } |
123 | 145 | ||
124 | async listForApi (options: { | 146 | async listForApi (options: { |
125 | state: JobState, | 147 | state: JobState |
126 | start: number, | 148 | start: number |
127 | count: number, | 149 | count: number |
128 | asc?: boolean, | 150 | asc?: boolean |
129 | jobType: JobType | 151 | jobType: JobType |
130 | }): Promise<Bull.Job[]> { | 152 | }): Promise<Bull.Job[]> { |
131 | const { state, start, count, asc, jobType } = options | 153 | const { state, start, count, asc, jobType } = options |
@@ -133,16 +155,14 @@ class JobQueue { | |||
133 | 155 | ||
134 | const filteredJobTypes = this.filterJobTypes(jobType) | 156 | const filteredJobTypes = this.filterJobTypes(jobType) |
135 | 157 | ||
136 | // TODO: optimize | ||
137 | for (const jobType of filteredJobTypes) { | 158 | for (const jobType of filteredJobTypes) { |
138 | const queue = this.queues[ jobType ] | 159 | const queue = this.queues[jobType] |
139 | if (queue === undefined) { | 160 | if (queue === undefined) { |
140 | logger.error('Unknown queue %s to list jobs.', jobType) | 161 | logger.error('Unknown queue %s to list jobs.', jobType) |
141 | continue | 162 | continue |
142 | } | 163 | } |
143 | 164 | ||
144 | // FIXME: Bull queue typings does not have getJobs method | 165 | const jobs = await queue.getJobs([ state ], 0, start + count, asc) |
145 | const jobs = await (queue as any).getJobs(state, 0, start + count, asc) | ||
146 | results = results.concat(jobs) | 166 | results = results.concat(jobs) |
147 | } | 167 | } |
148 | 168 | ||
@@ -164,7 +184,7 @@ class JobQueue { | |||
164 | const filteredJobTypes = this.filterJobTypes(jobType) | 184 | const filteredJobTypes = this.filterJobTypes(jobType) |
165 | 185 | ||
166 | for (const type of filteredJobTypes) { | 186 | for (const type of filteredJobTypes) { |
167 | const queue = this.queues[ type ] | 187 | const queue = this.queues[type] |
168 | if (queue === undefined) { | 188 | if (queue === undefined) { |
169 | logger.error('Unknown queue %s to count jobs.', type) | 189 | logger.error('Unknown queue %s to count jobs.', type) |
170 | continue | 190 | continue |
@@ -172,7 +192,7 @@ class JobQueue { | |||
172 | 192 | ||
173 | const counts = await queue.getJobCounts() | 193 | const counts = await queue.getJobCounts() |
174 | 194 | ||
175 | total += counts[ state ] | 195 | total += counts[state] |
176 | } | 196 | } |
177 | 197 | ||
178 | return total | 198 | return total |
@@ -188,7 +208,7 @@ class JobQueue { | |||
188 | private addRepeatableJobs () { | 208 | private addRepeatableJobs () { |
189 | this.queues['videos-views'].add({}, { | 209 | this.queues['videos-views'].add({}, { |
190 | repeat: REPEAT_JOBS['videos-views'] | 210 | repeat: REPEAT_JOBS['videos-views'] |
191 | }) | 211 | }).catch(err => logger.error('Cannot add repeatable job.', { err })) |
192 | } | 212 | } |
193 | 213 | ||
194 | private filterJobTypes (jobType?: JobType) { | 214 | private filterJobTypes (jobType?: JobType) { |
diff --git a/server/lib/moderation.ts b/server/lib/moderation.ts index b609f4585..55f7a985d 100644 --- a/server/lib/moderation.ts +++ b/server/lib/moderation.ts | |||
@@ -15,41 +15,41 @@ export type AcceptResult = { | |||
15 | 15 | ||
16 | // Can be filtered by plugins | 16 | // Can be filtered by plugins |
17 | function isLocalVideoAccepted (object: { | 17 | function isLocalVideoAccepted (object: { |
18 | videoBody: VideoCreate, | 18 | videoBody: VideoCreate |
19 | videoFile: Express.Multer.File & { duration?: number }, | 19 | videoFile: Express.Multer.File & { duration?: number } |
20 | user: UserModel | 20 | user: UserModel |
21 | }): AcceptResult { | 21 | }): AcceptResult { |
22 | return { accepted: true } | 22 | return { accepted: true } |
23 | } | 23 | } |
24 | 24 | ||
25 | function isLocalVideoThreadAccepted (_object: { | 25 | function isLocalVideoThreadAccepted (_object: { |
26 | commentBody: VideoCommentCreate, | 26 | commentBody: VideoCommentCreate |
27 | video: VideoModel, | 27 | video: VideoModel |
28 | user: UserModel | 28 | user: UserModel |
29 | }): AcceptResult { | 29 | }): AcceptResult { |
30 | return { accepted: true } | 30 | return { accepted: true } |
31 | } | 31 | } |
32 | 32 | ||
33 | function isLocalVideoCommentReplyAccepted (_object: { | 33 | function isLocalVideoCommentReplyAccepted (_object: { |
34 | commentBody: VideoCommentCreate, | 34 | commentBody: VideoCommentCreate |
35 | parentComment: VideoCommentModel, | 35 | parentComment: VideoCommentModel |
36 | video: VideoModel, | 36 | video: VideoModel |
37 | user: UserModel | 37 | user: UserModel |
38 | }): AcceptResult { | 38 | }): AcceptResult { |
39 | return { accepted: true } | 39 | return { accepted: true } |
40 | } | 40 | } |
41 | 41 | ||
42 | function isRemoteVideoAccepted (_object: { | 42 | function isRemoteVideoAccepted (_object: { |
43 | activity: ActivityCreate, | 43 | activity: ActivityCreate |
44 | videoAP: VideoTorrentObject, | 44 | videoAP: VideoTorrentObject |
45 | byActor: ActorModel | 45 | byActor: ActorModel |
46 | }): AcceptResult { | 46 | }): AcceptResult { |
47 | return { accepted: true } | 47 | return { accepted: true } |
48 | } | 48 | } |
49 | 49 | ||
50 | function isRemoteVideoCommentAccepted (_object: { | 50 | function isRemoteVideoCommentAccepted (_object: { |
51 | activity: ActivityCreate, | 51 | activity: ActivityCreate |
52 | commentAP: VideoCommentObject, | 52 | commentAP: VideoCommentObject |
53 | byActor: ActorModel | 53 | byActor: ActorModel |
54 | }): AcceptResult { | 54 | }): AcceptResult { |
55 | return { accepted: true } | 55 | return { accepted: true } |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 679b9bcf6..017739523 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -5,8 +5,7 @@ import { UserNotificationModel } from '../models/account/user-notification' | |||
5 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
6 | import { PeerTubeSocket } from './peertube-socket' | 6 | import { PeerTubeSocket } from './peertube-socket' |
7 | import { CONFIG } from '../initializers/config' | 7 | import { CONFIG } from '../initializers/config' |
8 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' | 8 | import { VideoPrivacy, VideoState, VideoAbuse } from '../../shared/models/videos' |
9 | import * as Bluebird from 'bluebird' | ||
10 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 9 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
11 | import { | 10 | import { |
12 | MCommentOwnerVideo, | 11 | MCommentOwnerVideo, |
@@ -17,7 +16,8 @@ import { | |||
17 | MVideoFullLight | 16 | MVideoFullLight |
18 | } from '../typings/models/video' | 17 | } from '../typings/models/video' |
19 | import { | 18 | import { |
20 | MUser, MUserAccount, | 19 | MUser, |
20 | MUserAccount, | ||
21 | MUserDefault, | 21 | MUserDefault, |
22 | MUserNotifSettingAccount, | 22 | MUserNotifSettingAccount, |
23 | MUserWithNotificationSetting, | 23 | MUserWithNotificationSetting, |
@@ -26,20 +26,21 @@ import { | |||
26 | import { MAccountDefault, MActorFollowFull } from '../typings/models' | 26 | import { MAccountDefault, MActorFollowFull } from '../typings/models' |
27 | import { MVideoImportVideo } from '@server/typings/models/video/video-import' | 27 | import { MVideoImportVideo } from '@server/typings/models/video/video-import' |
28 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' | 28 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' |
29 | import { getServerActor } from '@server/helpers/utils' | 29 | import { getServerActor } from '@server/models/application/application' |
30 | 30 | ||
31 | class Notifier { | 31 | class Notifier { |
32 | 32 | ||
33 | private static instance: Notifier | 33 | private static instance: Notifier |
34 | 34 | ||
35 | private constructor () {} | 35 | private constructor () { |
36 | } | ||
36 | 37 | ||
37 | notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { | 38 | notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { |
38 | // Only notify on public and published videos which are not blacklisted | 39 | // Only notify on public and published videos which are not blacklisted |
39 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return | 40 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return |
40 | 41 | ||
41 | this.notifySubscribersOfNewVideo(video) | 42 | this.notifySubscribersOfNewVideo(video) |
42 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) | 43 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) |
43 | } | 44 | } |
44 | 45 | ||
45 | notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { | 46 | notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { |
@@ -63,7 +64,9 @@ class Notifier { | |||
63 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | 64 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return |
64 | 65 | ||
65 | this.notifyOwnedVideoHasBeenPublished(video) | 66 | this.notifyOwnedVideoHasBeenPublished(video) |
66 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length | 67 | .catch(err => { |
68 | logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err }) | ||
69 | }) | ||
67 | } | 70 | } |
68 | 71 | ||
69 | notifyOnNewComment (comment: MCommentOwnerVideo): void { | 72 | notifyOnNewComment (comment: MCommentOwnerVideo): void { |
@@ -74,19 +77,19 @@ class Notifier { | |||
74 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) | 77 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) |
75 | } | 78 | } |
76 | 79 | ||
77 | notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { | 80 | notifyOnNewVideoAbuse (parameters: { videoAbuse: VideoAbuse, videoAbuseInstance: MVideoAbuseVideo, reporter: string }): void { |
78 | this.notifyModeratorsOfNewVideoAbuse(videoAbuse) | 81 | this.notifyModeratorsOfNewVideoAbuse(parameters) |
79 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) | 82 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', parameters.videoAbuseInstance.Video.url, { err })) |
80 | } | 83 | } |
81 | 84 | ||
82 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { | 85 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { |
83 | this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist) | 86 | this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist) |
84 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err })) | 87 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err })) |
85 | } | 88 | } |
86 | 89 | ||
87 | notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { | 90 | notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { |
88 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) | 91 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) |
89 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) | 92 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) |
90 | } | 93 | } |
91 | 94 | ||
92 | notifyOnVideoUnblacklist (video: MVideoFullLight): void { | 95 | notifyOnVideoUnblacklist (video: MVideoFullLight): void { |
@@ -96,7 +99,7 @@ class Notifier { | |||
96 | 99 | ||
97 | notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { | 100 | notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { |
98 | this.notifyOwnerVideoImportIsFinished(videoImport, success) | 101 | this.notifyOwnerVideoImportIsFinished(videoImport, success) |
99 | .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) | 102 | .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) |
100 | } | 103 | } |
101 | 104 | ||
102 | notifyOnNewUserRegistration (user: MUserDefault): void { | 105 | notifyOnNewUserRegistration (user: MUserDefault): void { |
@@ -106,14 +109,14 @@ class Notifier { | |||
106 | 109 | ||
107 | notifyOfNewUserFollow (actorFollow: MActorFollowFull): void { | 110 | notifyOfNewUserFollow (actorFollow: MActorFollowFull): void { |
108 | this.notifyUserOfNewActorFollow(actorFollow) | 111 | this.notifyUserOfNewActorFollow(actorFollow) |
109 | .catch(err => { | 112 | .catch(err => { |
110 | logger.error( | 113 | logger.error( |
111 | 'Cannot notify owner of channel %s of a new follow by %s.', | 114 | 'Cannot notify owner of channel %s of a new follow by %s.', |
112 | actorFollow.ActorFollowing.VideoChannel.getDisplayName(), | 115 | actorFollow.ActorFollowing.VideoChannel.getDisplayName(), |
113 | actorFollow.ActorFollower.Account.getDisplayName(), | 116 | actorFollow.ActorFollower.Account.getDisplayName(), |
114 | { err } | 117 | { err } |
115 | ) | 118 | ) |
116 | }) | 119 | }) |
117 | } | 120 | } |
118 | 121 | ||
119 | notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void { | 122 | notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void { |
@@ -347,11 +350,15 @@ class Notifier { | |||
347 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) | 350 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) |
348 | } | 351 | } |
349 | 352 | ||
350 | private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) { | 353 | private async notifyModeratorsOfNewVideoAbuse (parameters: { |
354 | videoAbuse: VideoAbuse | ||
355 | videoAbuseInstance: MVideoAbuseVideo | ||
356 | reporter: string | ||
357 | }) { | ||
351 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) | 358 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) |
352 | if (moderators.length === 0) return | 359 | if (moderators.length === 0) return |
353 | 360 | ||
354 | logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url) | 361 | logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, parameters.videoAbuseInstance.Video.url) |
355 | 362 | ||
356 | function settingGetter (user: MUserWithNotificationSetting) { | 363 | function settingGetter (user: MUserWithNotificationSetting) { |
357 | return user.NotificationSetting.videoAbuseAsModerator | 364 | return user.NotificationSetting.videoAbuseAsModerator |
@@ -361,15 +368,15 @@ class Notifier { | |||
361 | const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({ | 368 | const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({ |
362 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, | 369 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, |
363 | userId: user.id, | 370 | userId: user.id, |
364 | videoAbuseId: videoAbuse.id | 371 | videoAbuseId: parameters.videoAbuse.id |
365 | }) | 372 | }) |
366 | notification.VideoAbuse = videoAbuse | 373 | notification.VideoAbuse = parameters.videoAbuseInstance |
367 | 374 | ||
368 | return notification | 375 | return notification |
369 | } | 376 | } |
370 | 377 | ||
371 | function emailSender (emails: string[]) { | 378 | function emailSender (emails: string[]) { |
372 | return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, videoAbuse) | 379 | return Emailer.Instance.addVideoAbuseModeratorsNotification(emails, parameters) |
373 | } | 380 | } |
374 | 381 | ||
375 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 382 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
@@ -548,10 +555,10 @@ class Notifier { | |||
548 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 555 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
549 | } | 556 | } |
550 | 557 | ||
551 | private async notify <T extends MUserWithNotificationSetting> (options: { | 558 | private async notify<T extends MUserWithNotificationSetting> (options: { |
552 | users: T[], | 559 | users: T[] |
553 | notificationCreator: (user: T) => Promise<UserNotificationModelForApi>, | 560 | notificationCreator: (user: T) => Promise<UserNotificationModelForApi> |
554 | emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, | 561 | emailSender: (emails: string[]) => void |
555 | settingGetter: (user: T) => UserNotificationSettingValue | 562 | settingGetter: (user: T) => UserNotificationSettingValue |
556 | }) { | 563 | }) { |
557 | const emails: string[] = [] | 564 | const emails: string[] = [] |
@@ -569,7 +576,7 @@ class Notifier { | |||
569 | } | 576 | } |
570 | 577 | ||
571 | if (emails.length !== 0) { | 578 | if (emails.length !== 0) { |
572 | await options.emailSender(emails) | 579 | options.emailSender(emails) |
573 | } | 580 | } |
574 | } | 581 | } |
575 | 582 | ||
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index 086856f41..dbcba897a 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as express from 'express' |
2 | import { AccessDeniedError } from 'oauth2-server' | 2 | import { AccessDeniedError } from 'oauth2-server' |
3 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
4 | import { UserModel } from '../models/account/user' | 4 | import { UserModel } from '../models/account/user' |
@@ -9,6 +9,11 @@ import { Transaction } from 'sequelize' | |||
9 | import { CONFIG } from '../initializers/config' | 9 | import { CONFIG } from '../initializers/config' |
10 | import * as LRUCache from 'lru-cache' | 10 | import * as LRUCache from 'lru-cache' |
11 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | 11 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' |
12 | import { MUser } from '@server/typings/models/user/user' | ||
13 | import { UserAdminFlag } from '@shared/models/users/user-flag.model' | ||
14 | import { createUserAccountAndChannelAndPlaylist } from './user' | ||
15 | import { UserRole } from '@shared/models/users/user-role' | ||
16 | import { PluginManager } from '@server/lib/plugins/plugin-manager' | ||
12 | 17 | ||
13 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } | 18 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } |
14 | 19 | ||
@@ -41,22 +46,33 @@ function clearCacheByToken (token: string) { | |||
41 | } | 46 | } |
42 | } | 47 | } |
43 | 48 | ||
44 | function getAccessToken (bearerToken: string) { | 49 | async function getAccessToken (bearerToken: string) { |
45 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') | 50 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') |
46 | 51 | ||
47 | if (!bearerToken) return Bluebird.resolve(undefined) | 52 | if (!bearerToken) return undefined |
48 | 53 | ||
49 | if (accessTokenCache.has(bearerToken)) return Bluebird.resolve(accessTokenCache.get(bearerToken)) | 54 | let tokenModel: MOAuthTokenUser |
50 | 55 | ||
51 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) | 56 | if (accessTokenCache.has(bearerToken)) { |
52 | .then(tokenModel => { | 57 | tokenModel = accessTokenCache.get(bearerToken) |
53 | if (tokenModel) { | 58 | } else { |
54 | accessTokenCache.set(bearerToken, tokenModel) | 59 | tokenModel = await OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) |
55 | userHavingToken.set(tokenModel.userId, tokenModel.accessToken) | ||
56 | } | ||
57 | 60 | ||
58 | return tokenModel | 61 | if (tokenModel) { |
59 | }) | 62 | accessTokenCache.set(bearerToken, tokenModel) |
63 | userHavingToken.set(tokenModel.userId, tokenModel.accessToken) | ||
64 | } | ||
65 | } | ||
66 | |||
67 | if (!tokenModel) return undefined | ||
68 | |||
69 | if (tokenModel.User.pluginAuth) { | ||
70 | const valid = await PluginManager.Instance.isTokenValid(tokenModel, 'access') | ||
71 | |||
72 | if (valid !== true) return undefined | ||
73 | } | ||
74 | |||
75 | return tokenModel | ||
60 | } | 76 | } |
61 | 77 | ||
62 | function getClient (clientId: string, clientSecret: string) { | 78 | function getClient (clientId: string, clientSecret: string) { |
@@ -65,20 +81,52 @@ function getClient (clientId: string, clientSecret: string) { | |||
65 | return OAuthClientModel.getByIdAndSecret(clientId, clientSecret) | 81 | return OAuthClientModel.getByIdAndSecret(clientId, clientSecret) |
66 | } | 82 | } |
67 | 83 | ||
68 | function getRefreshToken (refreshToken: string) { | 84 | async function getRefreshToken (refreshToken: string) { |
69 | logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') | 85 | logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') |
70 | 86 | ||
71 | return OAuthTokenModel.getByRefreshTokenAndPopulateClient(refreshToken) | 87 | const tokenInfo = await OAuthTokenModel.getByRefreshTokenAndPopulateClient(refreshToken) |
88 | if (!tokenInfo) return undefined | ||
89 | |||
90 | const tokenModel = tokenInfo.token | ||
91 | |||
92 | if (tokenModel.User.pluginAuth) { | ||
93 | const valid = await PluginManager.Instance.isTokenValid(tokenModel, 'refresh') | ||
94 | |||
95 | if (valid !== true) return undefined | ||
96 | } | ||
97 | |||
98 | return tokenInfo | ||
72 | } | 99 | } |
73 | 100 | ||
74 | async function getUser (usernameOrEmail: string, password: string) { | 101 | async function getUser (usernameOrEmail?: string, password?: string) { |
102 | const res: express.Response = this.request.res | ||
103 | |||
104 | // Special treatment coming from a plugin | ||
105 | if (res.locals.bypassLogin && res.locals.bypassLogin.bypass === true) { | ||
106 | const obj = res.locals.bypassLogin | ||
107 | logger.info('Bypassing oauth login by plugin %s.', obj.pluginName) | ||
108 | |||
109 | let user = await UserModel.loadByEmail(obj.user.email) | ||
110 | if (!user) user = await createUserFromExternal(obj.pluginName, obj.user) | ||
111 | |||
112 | // If the user does not belongs to a plugin, it was created before its installation | ||
113 | // Then we just go through a regular login process | ||
114 | if (user.pluginAuth !== null) { | ||
115 | // This user does not belong to this plugin, skip it | ||
116 | if (user.pluginAuth !== obj.pluginName) return null | ||
117 | |||
118 | return user | ||
119 | } | ||
120 | } | ||
121 | |||
75 | logger.debug('Getting User (username/email: ' + usernameOrEmail + ', password: ******).') | 122 | logger.debug('Getting User (username/email: ' + usernameOrEmail + ', password: ******).') |
76 | 123 | ||
77 | const user = await UserModel.loadByUsernameOrEmail(usernameOrEmail) | 124 | const user = await UserModel.loadByUsernameOrEmail(usernameOrEmail) |
78 | if (!user) return null | 125 | // If we don't find the user, or if the user belongs to a plugin |
126 | if (!user || user.pluginAuth !== null) return null | ||
79 | 127 | ||
80 | const passwordMatch = await user.isPasswordMatch(password) | 128 | const passwordMatch = await user.isPasswordMatch(password) |
81 | if (passwordMatch === false) return null | 129 | if (passwordMatch !== true) return null |
82 | 130 | ||
83 | if (user.blocked) throw new AccessDeniedError('User is blocked.') | 131 | if (user.blocked) throw new AccessDeniedError('User is blocked.') |
84 | 132 | ||
@@ -89,29 +137,36 @@ async function getUser (usernameOrEmail: string, password: string) { | |||
89 | return user | 137 | return user |
90 | } | 138 | } |
91 | 139 | ||
92 | async function revokeToken (tokenInfo: TokenInfo) { | 140 | async function revokeToken (tokenInfo: { refreshToken: string }) { |
141 | const res: express.Response = this.request.res | ||
93 | const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) | 142 | const token = await OAuthTokenModel.getByRefreshTokenAndPopulateUser(tokenInfo.refreshToken) |
143 | |||
94 | if (token) { | 144 | if (token) { |
145 | if (res.locals.explicitLogout === true && token.User.pluginAuth && token.authName) { | ||
146 | PluginManager.Instance.onLogout(token.User.pluginAuth, token.authName, token.User) | ||
147 | } | ||
148 | |||
95 | clearCacheByToken(token.accessToken) | 149 | clearCacheByToken(token.accessToken) |
96 | 150 | ||
97 | token.destroy() | 151 | token.destroy() |
98 | .catch(err => logger.error('Cannot destroy token when revoking token.', { err })) | 152 | .catch(err => logger.error('Cannot destroy token when revoking token.', { err })) |
153 | |||
154 | return true | ||
99 | } | 155 | } |
100 | 156 | ||
101 | /* | 157 | return false |
102 | * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js | ||
103 | * "As per the discussion we need set older date | ||
104 | * revokeToken will expected return a boolean in future version | ||
105 | * https://github.com/oauthjs/node-oauth2-server/pull/274 | ||
106 | * https://github.com/oauthjs/node-oauth2-server/issues/290" | ||
107 | */ | ||
108 | const expiredToken = token | ||
109 | expiredToken.refreshTokenExpiresAt = new Date('2015-05-28T06:59:53.000Z') | ||
110 | |||
111 | return expiredToken | ||
112 | } | 158 | } |
113 | 159 | ||
114 | async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) { | 160 | async function saveToken (token: TokenInfo, client: OAuthClientModel, user: UserModel) { |
161 | const res: express.Response = this.request.res | ||
162 | |||
163 | let authName: string = null | ||
164 | if (res.locals.bypassLogin?.bypass === true) { | ||
165 | authName = res.locals.bypassLogin.authName | ||
166 | } else if (res.locals.refreshTokenAuthName) { | ||
167 | authName = res.locals.refreshTokenAuthName | ||
168 | } | ||
169 | |||
115 | logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') | 170 | logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') |
116 | 171 | ||
117 | const tokenToCreate = { | 172 | const tokenToCreate = { |
@@ -119,11 +174,16 @@ async function saveToken (token: TokenInfo, client: OAuthClientModel, user: User | |||
119 | accessTokenExpiresAt: token.accessTokenExpiresAt, | 174 | accessTokenExpiresAt: token.accessTokenExpiresAt, |
120 | refreshToken: token.refreshToken, | 175 | refreshToken: token.refreshToken, |
121 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, | 176 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, |
177 | authName, | ||
122 | oAuthClientId: client.id, | 178 | oAuthClientId: client.id, |
123 | userId: user.id | 179 | userId: user.id |
124 | } | 180 | } |
125 | 181 | ||
126 | const tokenCreated = await OAuthTokenModel.create(tokenToCreate) | 182 | const tokenCreated = await OAuthTokenModel.create(tokenToCreate) |
183 | |||
184 | user.lastLoginDate = new Date() | ||
185 | await user.save() | ||
186 | |||
127 | return Object.assign(tokenCreated, { client, user }) | 187 | return Object.assign(tokenCreated, { client, user }) |
128 | } | 188 | } |
129 | 189 | ||
@@ -141,3 +201,30 @@ export { | |||
141 | revokeToken, | 201 | revokeToken, |
142 | saveToken | 202 | saveToken |
143 | } | 203 | } |
204 | |||
205 | async function createUserFromExternal (pluginAuth: string, options: { | ||
206 | username: string | ||
207 | email: string | ||
208 | role: UserRole | ||
209 | displayName: string | ||
210 | }) { | ||
211 | const userToCreate = new UserModel({ | ||
212 | username: options.username, | ||
213 | password: null, | ||
214 | email: options.email, | ||
215 | nsfwPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | ||
216 | autoPlayVideo: true, | ||
217 | role: options.role, | ||
218 | videoQuota: CONFIG.USER.VIDEO_QUOTA, | ||
219 | videoQuotaDaily: CONFIG.USER.VIDEO_QUOTA_DAILY, | ||
220 | adminFlags: UserAdminFlag.NONE, | ||
221 | pluginAuth | ||
222 | }) as MUser | ||
223 | |||
224 | const { user } = await createUserAccountAndChannelAndPlaylist({ | ||
225 | userToCreate, | ||
226 | userDisplayName: options.displayName | ||
227 | }) | ||
228 | |||
229 | return user | ||
230 | } | ||
diff --git a/server/lib/plugins/hooks.ts b/server/lib/plugins/hooks.ts index bcc8c674e..aa92f03cc 100644 --- a/server/lib/plugins/hooks.ts +++ b/server/lib/plugins/hooks.ts | |||
@@ -25,7 +25,7 @@ const Hooks = { | |||
25 | }, | 25 | }, |
26 | 26 | ||
27 | runAction: <T, U extends ServerActionHookName>(hookName: U, params?: T) => { | 27 | runAction: <T, U extends ServerActionHookName>(hookName: U, params?: T) => { |
28 | PluginManager.Instance.runHook(hookName, params) | 28 | PluginManager.Instance.runHook(hookName, undefined, params) |
29 | .catch(err => logger.error('Fatal hook error.', { err })) | 29 | .catch(err => logger.error('Fatal hook error.', { err })) |
30 | } | 30 | } |
31 | } | 31 | } |
diff --git a/server/lib/plugins/plugin-helpers.ts b/server/lib/plugins/plugin-helpers.ts new file mode 100644 index 000000000..de82b4918 --- /dev/null +++ b/server/lib/plugins/plugin-helpers.ts | |||
@@ -0,0 +1,133 @@ | |||
1 | import { PeerTubeHelpers } from '@server/typings/plugins' | ||
2 | import { sequelizeTypescript } from '@server/initializers/database' | ||
3 | import { buildLogger } from '@server/helpers/logger' | ||
4 | import { VideoModel } from '@server/models/video/video' | ||
5 | import { WEBSERVER } from '@server/initializers/constants' | ||
6 | import { ServerModel } from '@server/models/server/server' | ||
7 | import { getServerActor } from '@server/models/application/application' | ||
8 | import { addServerInBlocklist, removeServerFromBlocklist, addAccountInBlocklist, removeAccountFromBlocklist } from '../blocklist' | ||
9 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' | ||
10 | import { AccountModel } from '@server/models/account/account' | ||
11 | import { VideoBlacklistCreate } from '@shared/models' | ||
12 | import { blacklistVideo, unblacklistVideo } from '../video-blacklist' | ||
13 | import { VideoBlacklistModel } from '@server/models/video/video-blacklist' | ||
14 | import { AccountBlocklistModel } from '@server/models/account/account-blocklist' | ||
15 | |||
16 | function buildPluginHelpers (npmName: string): PeerTubeHelpers { | ||
17 | const logger = buildPluginLogger(npmName) | ||
18 | |||
19 | const database = buildDatabaseHelpers() | ||
20 | const videos = buildVideosHelpers() | ||
21 | |||
22 | const config = buildConfigHelpers() | ||
23 | |||
24 | const server = buildServerHelpers() | ||
25 | |||
26 | const moderation = buildModerationHelpers() | ||
27 | |||
28 | return { | ||
29 | logger, | ||
30 | database, | ||
31 | videos, | ||
32 | config, | ||
33 | moderation, | ||
34 | server | ||
35 | } | ||
36 | } | ||
37 | |||
38 | export { | ||
39 | buildPluginHelpers | ||
40 | } | ||
41 | |||
42 | // --------------------------------------------------------------------------- | ||
43 | |||
44 | function buildPluginLogger (npmName: string) { | ||
45 | return buildLogger(npmName) | ||
46 | } | ||
47 | |||
48 | function buildDatabaseHelpers () { | ||
49 | return { | ||
50 | query: sequelizeTypescript.query.bind(sequelizeTypescript) | ||
51 | } | ||
52 | } | ||
53 | |||
54 | function buildServerHelpers () { | ||
55 | return { | ||
56 | getServerActor: () => getServerActor() | ||
57 | } | ||
58 | } | ||
59 | |||
60 | function buildVideosHelpers () { | ||
61 | return { | ||
62 | loadByUrl: (url: string) => { | ||
63 | return VideoModel.loadByUrl(url) | ||
64 | }, | ||
65 | |||
66 | removeVideo: (id: number) => { | ||
67 | return sequelizeTypescript.transaction(async t => { | ||
68 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(id, t) | ||
69 | |||
70 | await video.destroy({ transaction: t }) | ||
71 | }) | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | function buildModerationHelpers () { | ||
77 | return { | ||
78 | blockServer: async (options: { byAccountId: number, hostToBlock: string }) => { | ||
79 | const serverToBlock = await ServerModel.loadOrCreateByHost(options.hostToBlock) | ||
80 | |||
81 | await addServerInBlocklist(options.byAccountId, serverToBlock.id) | ||
82 | }, | ||
83 | |||
84 | unblockServer: async (options: { byAccountId: number, hostToUnblock: string }) => { | ||
85 | const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(options.byAccountId, options.hostToUnblock) | ||
86 | if (!serverBlock) return | ||
87 | |||
88 | await removeServerFromBlocklist(serverBlock) | ||
89 | }, | ||
90 | |||
91 | blockAccount: async (options: { byAccountId: number, handleToBlock: string }) => { | ||
92 | const accountToBlock = await AccountModel.loadByNameWithHost(options.handleToBlock) | ||
93 | if (!accountToBlock) return | ||
94 | |||
95 | await addAccountInBlocklist(options.byAccountId, accountToBlock.id) | ||
96 | }, | ||
97 | |||
98 | unblockAccount: async (options: { byAccountId: number, handleToUnblock: string }) => { | ||
99 | const targetAccount = await AccountModel.loadByNameWithHost(options.handleToUnblock) | ||
100 | if (!targetAccount) return | ||
101 | |||
102 | const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(options.byAccountId, targetAccount.id) | ||
103 | if (!accountBlock) return | ||
104 | |||
105 | await removeAccountFromBlocklist(accountBlock) | ||
106 | }, | ||
107 | |||
108 | blacklistVideo: async (options: { videoIdOrUUID: number | string, createOptions: VideoBlacklistCreate }) => { | ||
109 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) | ||
110 | if (!video) return | ||
111 | |||
112 | await blacklistVideo(video, options.createOptions) | ||
113 | }, | ||
114 | |||
115 | unblacklistVideo: async (options: { videoIdOrUUID: number | string }) => { | ||
116 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(options.videoIdOrUUID) | ||
117 | if (!video) return | ||
118 | |||
119 | const videoBlacklist = await VideoBlacklistModel.loadByVideoId(video.id) | ||
120 | if (!videoBlacklist) return | ||
121 | |||
122 | await unblacklistVideo(videoBlacklist, video) | ||
123 | } | ||
124 | } | ||
125 | } | ||
126 | |||
127 | function buildConfigHelpers () { | ||
128 | return { | ||
129 | getWebserverUrl () { | ||
130 | return WEBSERVER.URL | ||
131 | } | ||
132 | } | ||
133 | } | ||
diff --git a/server/lib/plugins/plugin-index.ts b/server/lib/plugins/plugin-index.ts index 25b4f3c61..170f0c7e2 100644 --- a/server/lib/plugins/plugin-index.ts +++ b/server/lib/plugins/plugin-index.ts | |||
@@ -27,11 +27,11 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) | |||
27 | const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins' | 27 | const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins' |
28 | 28 | ||
29 | try { | 29 | try { |
30 | const { body } = await doRequest({ uri, qs, json: true }) | 30 | const { body } = await doRequest<any>({ uri, qs, json: true }) |
31 | 31 | ||
32 | logger.debug('Got result from PeerTube index.', { body }) | 32 | logger.debug('Got result from PeerTube index.', { body }) |
33 | 33 | ||
34 | await addInstanceInformation(body) | 34 | addInstanceInformation(body) |
35 | 35 | ||
36 | return body as ResultList<PeerTubePluginIndex> | 36 | return body as ResultList<PeerTubePluginIndex> |
37 | } catch (err) { | 37 | } catch (err) { |
@@ -40,7 +40,7 @@ async function listAvailablePluginsFromIndex (options: PeertubePluginIndexList) | |||
40 | } | 40 | } |
41 | } | 41 | } |
42 | 42 | ||
43 | async function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) { | 43 | function addInstanceInformation (result: ResultList<PeerTubePluginIndex>) { |
44 | for (const d of result.data) { | 44 | for (const d of result.data) { |
45 | d.installed = PluginManager.Instance.isRegistered(d.npmName) | 45 | d.installed = PluginManager.Instance.isRegistered(d.npmName) |
46 | d.name = PluginModel.normalizePluginName(d.npmName) | 46 | d.name = PluginModel.normalizePluginName(d.npmName) |
@@ -57,7 +57,7 @@ async function getLatestPluginsVersion (npmNames: string[]): Promise<PeertubePlu | |||
57 | 57 | ||
58 | const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins/latest-version' | 58 | const uri = CONFIG.PLUGINS.INDEX.URL + '/api/v1/plugins/latest-version' |
59 | 59 | ||
60 | const { body } = await doRequest({ uri, body: bodyRequest, json: true, method: 'POST' }) | 60 | const { body } = await doRequest<any>({ uri, body: bodyRequest, json: true, method: 'POST' }) |
61 | 61 | ||
62 | return body | 62 | return body |
63 | } | 63 | } |
diff --git a/server/lib/plugins/plugin-manager.ts b/server/lib/plugins/plugin-manager.ts index 7ebdabd34..950acf7ad 100644 --- a/server/lib/plugins/plugin-manager.ts +++ b/server/lib/plugins/plugin-manager.ts | |||
@@ -9,23 +9,20 @@ import { | |||
9 | PluginTranslationPaths as PackagePluginTranslations | 9 | PluginTranslationPaths as PackagePluginTranslations |
10 | } from '../../../shared/models/plugins/plugin-package-json.model' | 10 | } from '../../../shared/models/plugins/plugin-package-json.model' |
11 | import { createReadStream, createWriteStream } from 'fs' | 11 | import { createReadStream, createWriteStream } from 'fs' |
12 | import { PLUGIN_GLOBAL_CSS_PATH, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../../initializers/constants' | 12 | import { PLUGIN_GLOBAL_CSS_PATH } from '../../initializers/constants' |
13 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 13 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
14 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' | 14 | import { installNpmPlugin, installNpmPluginFromDisk, removeNpmPlugin } from './yarn' |
15 | import { outputFile, readJSON } from 'fs-extra' | 15 | import { outputFile, readJSON } from 'fs-extra' |
16 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' | 16 | import { ServerHook, ServerHookName } from '../../../shared/models/plugins/server-hook.model' |
17 | import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' | ||
18 | import { ServerHook, ServerHookName, serverHookObject } from '../../../shared/models/plugins/server-hook.model' | ||
19 | import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' | 17 | import { getHookType, internalRunHook } from '../../../shared/core-utils/plugins/hooks' |
20 | import { RegisterServerOptions } from '../../typings/plugins/register-server-option.model' | 18 | import { RegisterServerOptions } from '../../typings/plugins/register-server-option.model' |
21 | import { PluginLibrary } from '../../typings/plugins' | 19 | import { PluginLibrary } from '../../typings/plugins' |
22 | import { ClientHtml } from '../client-html' | 20 | import { ClientHtml } from '../client-html' |
23 | import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' | ||
24 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | ||
25 | import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model' | ||
26 | import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model' | ||
27 | import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model' | ||
28 | import { PluginTranslation } from '../../../shared/models/plugins/plugin-translation.model' | 21 | import { PluginTranslation } from '../../../shared/models/plugins/plugin-translation.model' |
22 | import { RegisterHelpersStore } from './register-helpers-store' | ||
23 | import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' | ||
24 | import { MOAuthTokenUser, MUser } from '@server/typings/models' | ||
25 | import { RegisterServerAuthPassOptions, RegisterServerAuthExternalOptions } from '@shared/models/plugins/register-server-auth.model' | ||
29 | 26 | ||
30 | export interface RegisteredPlugin { | 27 | export interface RegisteredPlugin { |
31 | npmName: string | 28 | npmName: string |
@@ -44,6 +41,7 @@ export interface RegisteredPlugin { | |||
44 | css: string[] | 41 | css: string[] |
45 | 42 | ||
46 | // Only if this is a plugin | 43 | // Only if this is a plugin |
44 | registerHelpersStore?: RegisterHelpersStore | ||
47 | unregister?: Function | 45 | unregister?: Function |
48 | } | 46 | } |
49 | 47 | ||
@@ -54,35 +52,18 @@ export interface HookInformationValue { | |||
54 | priority: number | 52 | priority: number |
55 | } | 53 | } |
56 | 54 | ||
57 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | ||
58 | type VideoConstant = { [ key in number | string ]: string } | ||
59 | type UpdatedVideoConstant = { | ||
60 | [ name in AlterableVideoConstant ]: { | ||
61 | [ npmName: string ]: { | ||
62 | added: { key: number | string, label: string }[], | ||
63 | deleted: { key: number | string, label: string }[] | ||
64 | } | ||
65 | } | ||
66 | } | ||
67 | |||
68 | type PluginLocalesTranslations = { | 55 | type PluginLocalesTranslations = { |
69 | [ locale: string ]: PluginTranslation | 56 | [locale: string]: PluginTranslation |
70 | } | 57 | } |
71 | 58 | ||
72 | export class PluginManager implements ServerHook { | 59 | export class PluginManager implements ServerHook { |
73 | 60 | ||
74 | private static instance: PluginManager | 61 | private static instance: PluginManager |
75 | 62 | ||
76 | private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {} | 63 | private registeredPlugins: { [name: string]: RegisteredPlugin } = {} |
77 | private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {} | ||
78 | private hooks: { [ name: string ]: HookInformationValue[] } = {} | ||
79 | private translations: PluginLocalesTranslations = {} | ||
80 | 64 | ||
81 | private updatedVideoConstants: UpdatedVideoConstant = { | 65 | private hooks: { [name: string]: HookInformationValue[] } = {} |
82 | language: {}, | 66 | private translations: PluginLocalesTranslations = {} |
83 | licence: {}, | ||
84 | category: {} | ||
85 | } | ||
86 | 67 | ||
87 | private constructor () { | 68 | private constructor () { |
88 | } | 69 | } |
@@ -97,7 +78,7 @@ export class PluginManager implements ServerHook { | |||
97 | return this.registeredPlugins[npmName] | 78 | return this.registeredPlugins[npmName] |
98 | } | 79 | } |
99 | 80 | ||
100 | getRegisteredPlugin (name: string) { | 81 | getRegisteredPluginByShortName (name: string) { |
101 | const npmName = PluginModel.buildNpmName(name, PluginType.PLUGIN) | 82 | const npmName = PluginModel.buildNpmName(name, PluginType.PLUGIN) |
102 | const registered = this.getRegisteredPluginOrTheme(npmName) | 83 | const registered = this.getRegisteredPluginOrTheme(npmName) |
103 | 84 | ||
@@ -106,7 +87,7 @@ export class PluginManager implements ServerHook { | |||
106 | return registered | 87 | return registered |
107 | } | 88 | } |
108 | 89 | ||
109 | getRegisteredTheme (name: string) { | 90 | getRegisteredThemeByShortName (name: string) { |
110 | const npmName = PluginModel.buildNpmName(name, PluginType.THEME) | 91 | const npmName = PluginModel.buildNpmName(name, PluginType.THEME) |
111 | const registered = this.getRegisteredPluginOrTheme(npmName) | 92 | const registered = this.getRegisteredPluginOrTheme(npmName) |
112 | 93 | ||
@@ -123,17 +104,102 @@ export class PluginManager implements ServerHook { | |||
123 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) | 104 | return this.getRegisteredPluginsOrThemes(PluginType.THEME) |
124 | } | 105 | } |
125 | 106 | ||
107 | getIdAndPassAuths () { | ||
108 | return this.getRegisteredPlugins() | ||
109 | .map(p => ({ | ||
110 | npmName: p.npmName, | ||
111 | name: p.name, | ||
112 | version: p.version, | ||
113 | idAndPassAuths: p.registerHelpersStore.getIdAndPassAuths() | ||
114 | })) | ||
115 | .filter(v => v.idAndPassAuths.length !== 0) | ||
116 | } | ||
117 | |||
118 | getExternalAuths () { | ||
119 | return this.getRegisteredPlugins() | ||
120 | .map(p => ({ | ||
121 | npmName: p.npmName, | ||
122 | name: p.name, | ||
123 | version: p.version, | ||
124 | externalAuths: p.registerHelpersStore.getExternalAuths() | ||
125 | })) | ||
126 | .filter(v => v.externalAuths.length !== 0) | ||
127 | } | ||
128 | |||
126 | getRegisteredSettings (npmName: string) { | 129 | getRegisteredSettings (npmName: string) { |
127 | return this.settings[npmName] || [] | 130 | const result = this.getRegisteredPluginOrTheme(npmName) |
131 | if (!result || result.type !== PluginType.PLUGIN) return [] | ||
132 | |||
133 | return result.registerHelpersStore.getSettings() | ||
134 | } | ||
135 | |||
136 | getRouter (npmName: string) { | ||
137 | const result = this.getRegisteredPluginOrTheme(npmName) | ||
138 | if (!result || result.type !== PluginType.PLUGIN) return null | ||
139 | |||
140 | return result.registerHelpersStore.getRouter() | ||
128 | } | 141 | } |
129 | 142 | ||
130 | getTranslations (locale: string) { | 143 | getTranslations (locale: string) { |
131 | return this.translations[locale] || {} | 144 | return this.translations[locale] || {} |
132 | } | 145 | } |
133 | 146 | ||
147 | async isTokenValid (token: MOAuthTokenUser, type: 'access' | 'refresh') { | ||
148 | const auth = this.getAuth(token.User.pluginAuth, token.authName) | ||
149 | if (!auth) return true | ||
150 | |||
151 | if (auth.hookTokenValidity) { | ||
152 | try { | ||
153 | const { valid } = await auth.hookTokenValidity({ token, type }) | ||
154 | |||
155 | if (valid === false) { | ||
156 | logger.info('Rejecting %s token validity from auth %s of plugin %s', type, token.authName, token.User.pluginAuth) | ||
157 | } | ||
158 | |||
159 | return valid | ||
160 | } catch (err) { | ||
161 | logger.warn('Cannot run check token validity from auth %s of plugin %s.', token.authName, token.User.pluginAuth, { err }) | ||
162 | return true | ||
163 | } | ||
164 | } | ||
165 | |||
166 | return true | ||
167 | } | ||
168 | |||
169 | // ###################### External events ###################### | ||
170 | |||
171 | onLogout (npmName: string, authName: string, user: MUser) { | ||
172 | const auth = this.getAuth(npmName, authName) | ||
173 | |||
174 | if (auth?.onLogout) { | ||
175 | logger.info('Running onLogout function from auth %s of plugin %s', authName, npmName) | ||
176 | |||
177 | try { | ||
178 | auth.onLogout(user) | ||
179 | } catch (err) { | ||
180 | logger.warn('Cannot run onLogout function from auth %s of plugin %s.', authName, npmName, { err }) | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | |||
185 | onSettingsChanged (name: string, settings: any) { | ||
186 | const registered = this.getRegisteredPluginByShortName(name) | ||
187 | if (!registered) { | ||
188 | logger.error('Cannot find plugin %s to call on settings changed.', name) | ||
189 | } | ||
190 | |||
191 | for (const cb of registered.registerHelpersStore.getOnSettingsChangedCallbacks()) { | ||
192 | try { | ||
193 | cb(settings) | ||
194 | } catch (err) { | ||
195 | logger.error('Cannot run on settings changed callback for %s.', registered.npmName, { err }) | ||
196 | } | ||
197 | } | ||
198 | } | ||
199 | |||
134 | // ###################### Hooks ###################### | 200 | // ###################### Hooks ###################### |
135 | 201 | ||
136 | async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { | 202 | async runHook<T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> { |
137 | if (!this.hooks[hookName]) return Promise.resolve(result) | 203 | if (!this.hooks[hookName]) return Promise.resolve(result) |
138 | 204 | ||
139 | const hookType = getHookType(hookName) | 205 | const hookType = getHookType(hookName) |
@@ -185,7 +251,6 @@ export class PluginManager implements ServerHook { | |||
185 | } | 251 | } |
186 | 252 | ||
187 | delete this.registeredPlugins[plugin.npmName] | 253 | delete this.registeredPlugins[plugin.npmName] |
188 | delete this.settings[plugin.npmName] | ||
189 | 254 | ||
190 | this.deleteTranslations(plugin.npmName) | 255 | this.deleteTranslations(plugin.npmName) |
191 | 256 | ||
@@ -197,7 +262,8 @@ export class PluginManager implements ServerHook { | |||
197 | this.hooks[key] = this.hooks[key].filter(h => h.npmName !== npmName) | 262 | this.hooks[key] = this.hooks[key].filter(h => h.npmName !== npmName) |
198 | } | 263 | } |
199 | 264 | ||
200 | this.reinitVideoConstants(plugin.npmName) | 265 | const store = plugin.registerHelpersStore |
266 | store.reinitVideoConstants(plugin.npmName) | ||
201 | 267 | ||
202 | logger.info('Regenerating registered plugin CSS to global file.') | 268 | logger.info('Regenerating registered plugin CSS to global file.') |
203 | await this.regeneratePluginGlobalCSS() | 269 | await this.regeneratePluginGlobalCSS() |
@@ -303,8 +369,11 @@ export class PluginManager implements ServerHook { | |||
303 | this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, plugin.type) | 369 | this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, plugin.type) |
304 | 370 | ||
305 | let library: PluginLibrary | 371 | let library: PluginLibrary |
372 | let registerHelpersStore: RegisterHelpersStore | ||
306 | if (plugin.type === PluginType.PLUGIN) { | 373 | if (plugin.type === PluginType.PLUGIN) { |
307 | library = await this.registerPlugin(plugin, pluginPath, packageJSON) | 374 | const result = await this.registerPlugin(plugin, pluginPath, packageJSON) |
375 | library = result.library | ||
376 | registerHelpersStore = result.registerStore | ||
308 | } | 377 | } |
309 | 378 | ||
310 | const clientScripts: { [id: string]: ClientScript } = {} | 379 | const clientScripts: { [id: string]: ClientScript } = {} |
@@ -312,7 +381,7 @@ export class PluginManager implements ServerHook { | |||
312 | clientScripts[c.script] = c | 381 | clientScripts[c.script] = c |
313 | } | 382 | } |
314 | 383 | ||
315 | this.registeredPlugins[ npmName ] = { | 384 | this.registeredPlugins[npmName] = { |
316 | npmName, | 385 | npmName, |
317 | name: plugin.name, | 386 | name: plugin.name, |
318 | type: plugin.type, | 387 | type: plugin.type, |
@@ -323,6 +392,7 @@ export class PluginManager implements ServerHook { | |||
323 | staticDirs: packageJSON.staticDirs, | 392 | staticDirs: packageJSON.staticDirs, |
324 | clientScripts, | 393 | clientScripts, |
325 | css: packageJSON.css, | 394 | css: packageJSON.css, |
395 | registerHelpersStore: registerHelpersStore || undefined, | ||
326 | unregister: library ? library.unregister : undefined | 396 | unregister: library ? library.unregister : undefined |
327 | } | 397 | } |
328 | 398 | ||
@@ -341,15 +411,15 @@ export class PluginManager implements ServerHook { | |||
341 | throw new Error('Library code is not valid (miss register or unregister function)') | 411 | throw new Error('Library code is not valid (miss register or unregister function)') |
342 | } | 412 | } |
343 | 413 | ||
344 | const registerHelpers = this.getRegisterHelpers(npmName, plugin) | 414 | const { registerOptions, registerStore } = this.getRegisterHelpers(npmName, plugin) |
345 | library.register(registerHelpers) | 415 | library.register(registerOptions) |
346 | .catch(err => logger.error('Cannot register plugin %s.', npmName, { err })) | 416 | .catch(err => logger.error('Cannot register plugin %s.', npmName, { err })) |
347 | 417 | ||
348 | logger.info('Add plugin %s CSS to global file.', npmName) | 418 | logger.info('Add plugin %s CSS to global file.', npmName) |
349 | 419 | ||
350 | await this.addCSSToGlobalFile(pluginPath, packageJSON.css) | 420 | await this.addCSSToGlobalFile(pluginPath, packageJSON.css) |
351 | 421 | ||
352 | return library | 422 | return { library, registerStore } |
353 | } | 423 | } |
354 | 424 | ||
355 | // ###################### Translations ###################### | 425 | // ###################### Translations ###################### |
@@ -432,13 +502,23 @@ export class PluginManager implements ServerHook { | |||
432 | return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', npmName) | 502 | return join(CONFIG.STORAGE.PLUGINS_DIR, 'node_modules', npmName) |
433 | } | 503 | } |
434 | 504 | ||
505 | private getAuth (npmName: string, authName: string) { | ||
506 | const plugin = this.getRegisteredPluginOrTheme(npmName) | ||
507 | if (!plugin || plugin.type !== PluginType.PLUGIN) return null | ||
508 | |||
509 | let auths: (RegisterServerAuthPassOptions | RegisterServerAuthExternalOptions)[] = plugin.registerHelpersStore.getIdAndPassAuths() | ||
510 | auths = auths.concat(plugin.registerHelpersStore.getExternalAuths()) | ||
511 | |||
512 | return auths.find(a => a.authName === authName) | ||
513 | } | ||
514 | |||
435 | // ###################### Private getters ###################### | 515 | // ###################### Private getters ###################### |
436 | 516 | ||
437 | private getRegisteredPluginsOrThemes (type: PluginType) { | 517 | private getRegisteredPluginsOrThemes (type: PluginType) { |
438 | const plugins: RegisteredPlugin[] = [] | 518 | const plugins: RegisteredPlugin[] = [] |
439 | 519 | ||
440 | for (const npmName of Object.keys(this.registeredPlugins)) { | 520 | for (const npmName of Object.keys(this.registeredPlugins)) { |
441 | const plugin = this.registeredPlugins[ npmName ] | 521 | const plugin = this.registeredPlugins[npmName] |
442 | if (plugin.type !== type) continue | 522 | if (plugin.type !== type) continue |
443 | 523 | ||
444 | plugins.push(plugin) | 524 | plugins.push(plugin) |
@@ -449,149 +529,26 @@ export class PluginManager implements ServerHook { | |||
449 | 529 | ||
450 | // ###################### Generate register helpers ###################### | 530 | // ###################### Generate register helpers ###################### |
451 | 531 | ||
452 | private getRegisterHelpers (npmName: string, plugin: PluginModel): RegisterServerOptions { | 532 | private getRegisterHelpers ( |
453 | const registerHook = (options: RegisterServerHookOptions) => { | 533 | npmName: string, |
454 | if (serverHookObject[options.target] !== true) { | 534 | plugin: PluginModel |
455 | logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, npmName) | 535 | ): { registerStore: RegisterHelpersStore, registerOptions: RegisterServerOptions } { |
456 | return | 536 | const onHookAdded = (options: RegisterServerHookOptions) => { |
457 | } | ||
458 | |||
459 | if (!this.hooks[options.target]) this.hooks[options.target] = [] | 537 | if (!this.hooks[options.target]) this.hooks[options.target] = [] |
460 | 538 | ||
461 | this.hooks[options.target].push({ | 539 | this.hooks[options.target].push({ |
462 | npmName, | 540 | npmName: npmName, |
463 | pluginName: plugin.name, | 541 | pluginName: plugin.name, |
464 | handler: options.handler, | 542 | handler: options.handler, |
465 | priority: options.priority || 0 | 543 | priority: options.priority || 0 |
466 | }) | 544 | }) |
467 | } | 545 | } |
468 | 546 | ||
469 | const registerSetting = (options: RegisterServerSettingOptions) => { | 547 | const registerHelpersStore = new RegisterHelpersStore(npmName, plugin, onHookAdded.bind(this)) |
470 | if (!this.settings[npmName]) this.settings[npmName] = [] | ||
471 | |||
472 | this.settings[npmName].push(options) | ||
473 | } | ||
474 | |||
475 | const settingsManager: PluginSettingsManager = { | ||
476 | getSetting: (name: string) => PluginModel.getSetting(plugin.name, plugin.type, name), | ||
477 | |||
478 | setSetting: (name: string, value: string) => PluginModel.setSetting(plugin.name, plugin.type, name, value) | ||
479 | } | ||
480 | |||
481 | const storageManager: PluginStorageManager = { | ||
482 | getData: (key: string) => PluginModel.getData(plugin.name, plugin.type, key), | ||
483 | |||
484 | storeData: (key: string, data: any) => PluginModel.storeData(plugin.name, plugin.type, key, data) | ||
485 | } | ||
486 | |||
487 | const videoLanguageManager: PluginVideoLanguageManager = { | ||
488 | addLanguage: (key: string, label: string) => this.addConstant({ npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label }), | ||
489 | |||
490 | deleteLanguage: (key: string) => this.deleteConstant({ npmName, type: 'language', obj: VIDEO_LANGUAGES, key }) | ||
491 | } | ||
492 | |||
493 | const videoCategoryManager: PluginVideoCategoryManager = { | ||
494 | addCategory: (key: number, label: string) => this.addConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }), | ||
495 | |||
496 | deleteCategory: (key: number) => this.deleteConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key }) | ||
497 | } | ||
498 | |||
499 | const videoLicenceManager: PluginVideoLicenceManager = { | ||
500 | addLicence: (key: number, label: string) => this.addConstant({ npmName, type: 'licence', obj: VIDEO_LICENCES, key, label }), | ||
501 | |||
502 | deleteLicence: (key: number) => this.deleteConstant({ npmName, type: 'licence', obj: VIDEO_LICENCES, key }) | ||
503 | } | ||
504 | |||
505 | const peertubeHelpers = { | ||
506 | logger | ||
507 | } | ||
508 | 548 | ||
509 | return { | 549 | return { |
510 | registerHook, | 550 | registerStore: registerHelpersStore, |
511 | registerSetting, | 551 | registerOptions: registerHelpersStore.buildRegisterHelpers() |
512 | settingsManager, | ||
513 | storageManager, | ||
514 | videoLanguageManager, | ||
515 | videoCategoryManager, | ||
516 | videoLicenceManager, | ||
517 | peertubeHelpers | ||
518 | } | ||
519 | } | ||
520 | |||
521 | private addConstant <T extends string | number> (parameters: { | ||
522 | npmName: string, | ||
523 | type: AlterableVideoConstant, | ||
524 | obj: VideoConstant, | ||
525 | key: T, | ||
526 | label: string | ||
527 | }) { | ||
528 | const { npmName, type, obj, key, label } = parameters | ||
529 | |||
530 | if (obj[key]) { | ||
531 | logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key) | ||
532 | return false | ||
533 | } | ||
534 | |||
535 | if (!this.updatedVideoConstants[type][npmName]) { | ||
536 | this.updatedVideoConstants[type][npmName] = { | ||
537 | added: [], | ||
538 | deleted: [] | ||
539 | } | ||
540 | } | ||
541 | |||
542 | this.updatedVideoConstants[type][npmName].added.push({ key, label }) | ||
543 | obj[key] = label | ||
544 | |||
545 | return true | ||
546 | } | ||
547 | |||
548 | private deleteConstant <T extends string | number> (parameters: { | ||
549 | npmName: string, | ||
550 | type: AlterableVideoConstant, | ||
551 | obj: VideoConstant, | ||
552 | key: T | ||
553 | }) { | ||
554 | const { npmName, type, obj, key } = parameters | ||
555 | |||
556 | if (!obj[key]) { | ||
557 | logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key) | ||
558 | return false | ||
559 | } | ||
560 | |||
561 | if (!this.updatedVideoConstants[type][npmName]) { | ||
562 | this.updatedVideoConstants[type][npmName] = { | ||
563 | added: [], | ||
564 | deleted: [] | ||
565 | } | ||
566 | } | ||
567 | |||
568 | this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] }) | ||
569 | delete obj[key] | ||
570 | |||
571 | return true | ||
572 | } | ||
573 | |||
574 | private reinitVideoConstants (npmName: string) { | ||
575 | const hash = { | ||
576 | language: VIDEO_LANGUAGES, | ||
577 | licence: VIDEO_LICENCES, | ||
578 | category: VIDEO_CATEGORIES | ||
579 | } | ||
580 | const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category' ] | ||
581 | |||
582 | for (const type of types) { | ||
583 | const updatedConstants = this.updatedVideoConstants[type][npmName] | ||
584 | if (!updatedConstants) continue | ||
585 | |||
586 | for (const added of updatedConstants.added) { | ||
587 | delete hash[type][added.key] | ||
588 | } | ||
589 | |||
590 | for (const deleted of updatedConstants.deleted) { | ||
591 | hash[type][deleted.key] = deleted.label | ||
592 | } | ||
593 | |||
594 | delete this.updatedVideoConstants[type][npmName] | ||
595 | } | 552 | } |
596 | } | 553 | } |
597 | 554 | ||
@@ -604,7 +561,7 @@ export class PluginManager implements ServerHook { | |||
604 | const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) | 561 | const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType) |
605 | if (!packageJSONValid) { | 562 | if (!packageJSONValid) { |
606 | const formattedFields = badFields.map(f => `"${f}"`) | 563 | const formattedFields = badFields.map(f => `"${f}"`) |
607 | .join(', ') | 564 | .join(', ') |
608 | 565 | ||
609 | throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) | 566 | throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`) |
610 | } | 567 | } |
diff --git a/server/lib/plugins/register-helpers-store.ts b/server/lib/plugins/register-helpers-store.ts new file mode 100644 index 000000000..e337b1cb0 --- /dev/null +++ b/server/lib/plugins/register-helpers-store.ts | |||
@@ -0,0 +1,355 @@ | |||
1 | import * as express from 'express' | ||
2 | import { logger } from '@server/helpers/logger' | ||
3 | import { | ||
4 | VIDEO_CATEGORIES, | ||
5 | VIDEO_LANGUAGES, | ||
6 | VIDEO_LICENCES, | ||
7 | VIDEO_PLAYLIST_PRIVACIES, | ||
8 | VIDEO_PRIVACIES | ||
9 | } from '@server/initializers/constants' | ||
10 | import { onExternalUserAuthenticated } from '@server/lib/auth' | ||
11 | import { PluginModel } from '@server/models/server/plugin' | ||
12 | import { RegisterServerOptions } from '@server/typings/plugins' | ||
13 | import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model' | ||
14 | import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model' | ||
15 | import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model' | ||
16 | import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-manager.model' | ||
17 | import { PluginVideoLanguageManager } from '@shared/models/plugins/plugin-video-language-manager.model' | ||
18 | import { PluginVideoLicenceManager } from '@shared/models/plugins/plugin-video-licence-manager.model' | ||
19 | import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' | ||
20 | import { | ||
21 | RegisterServerAuthExternalOptions, | ||
22 | RegisterServerAuthExternalResult, | ||
23 | RegisterServerAuthPassOptions, | ||
24 | RegisterServerExternalAuthenticatedResult | ||
25 | } from '@shared/models/plugins/register-server-auth.model' | ||
26 | import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model' | ||
27 | import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model' | ||
28 | import { serverHookObject } from '@shared/models/plugins/server-hook.model' | ||
29 | import { buildPluginHelpers } from './plugin-helpers' | ||
30 | |||
31 | type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy' | ||
32 | type VideoConstant = { [key in number | string]: string } | ||
33 | |||
34 | type UpdatedVideoConstant = { | ||
35 | [name in AlterableVideoConstant]: { | ||
36 | added: { key: number | string, label: string }[] | ||
37 | deleted: { key: number | string, label: string }[] | ||
38 | } | ||
39 | } | ||
40 | |||
41 | export class RegisterHelpersStore { | ||
42 | private readonly updatedVideoConstants: UpdatedVideoConstant = { | ||
43 | playlistPrivacy: { added: [], deleted: [] }, | ||
44 | privacy: { added: [], deleted: [] }, | ||
45 | language: { added: [], deleted: [] }, | ||
46 | licence: { added: [], deleted: [] }, | ||
47 | category: { added: [], deleted: [] } | ||
48 | } | ||
49 | |||
50 | private readonly settings: RegisterServerSettingOptions[] = [] | ||
51 | |||
52 | private idAndPassAuths: RegisterServerAuthPassOptions[] = [] | ||
53 | private externalAuths: RegisterServerAuthExternalOptions[] = [] | ||
54 | |||
55 | private readonly onSettingsChangeCallbacks: ((settings: any) => void)[] = [] | ||
56 | |||
57 | private readonly router: express.Router | ||
58 | |||
59 | constructor ( | ||
60 | private readonly npmName: string, | ||
61 | private readonly plugin: PluginModel, | ||
62 | private readonly onHookAdded: (options: RegisterServerHookOptions) => void | ||
63 | ) { | ||
64 | this.router = express.Router() | ||
65 | } | ||
66 | |||
67 | buildRegisterHelpers (): RegisterServerOptions { | ||
68 | const registerHook = this.buildRegisterHook() | ||
69 | const registerSetting = this.buildRegisterSetting() | ||
70 | |||
71 | const getRouter = this.buildGetRouter() | ||
72 | |||
73 | const settingsManager = this.buildSettingsManager() | ||
74 | const storageManager = this.buildStorageManager() | ||
75 | |||
76 | const videoLanguageManager = this.buildVideoLanguageManager() | ||
77 | |||
78 | const videoLicenceManager = this.buildVideoLicenceManager() | ||
79 | const videoCategoryManager = this.buildVideoCategoryManager() | ||
80 | |||
81 | const videoPrivacyManager = this.buildVideoPrivacyManager() | ||
82 | const playlistPrivacyManager = this.buildPlaylistPrivacyManager() | ||
83 | |||
84 | const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth() | ||
85 | const registerExternalAuth = this.buildRegisterExternalAuth() | ||
86 | const unregisterIdAndPassAuth = this.buildUnregisterIdAndPassAuth() | ||
87 | const unregisterExternalAuth = this.buildUnregisterExternalAuth() | ||
88 | |||
89 | const peertubeHelpers = buildPluginHelpers(this.npmName) | ||
90 | |||
91 | return { | ||
92 | registerHook, | ||
93 | registerSetting, | ||
94 | |||
95 | getRouter, | ||
96 | |||
97 | settingsManager, | ||
98 | storageManager, | ||
99 | |||
100 | videoLanguageManager, | ||
101 | videoCategoryManager, | ||
102 | videoLicenceManager, | ||
103 | |||
104 | videoPrivacyManager, | ||
105 | playlistPrivacyManager, | ||
106 | |||
107 | registerIdAndPassAuth, | ||
108 | registerExternalAuth, | ||
109 | unregisterIdAndPassAuth, | ||
110 | unregisterExternalAuth, | ||
111 | |||
112 | peertubeHelpers | ||
113 | } | ||
114 | } | ||
115 | |||
116 | reinitVideoConstants (npmName: string) { | ||
117 | const hash = { | ||
118 | language: VIDEO_LANGUAGES, | ||
119 | licence: VIDEO_LICENCES, | ||
120 | category: VIDEO_CATEGORIES, | ||
121 | privacy: VIDEO_PRIVACIES, | ||
122 | playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES | ||
123 | } | ||
124 | const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ] | ||
125 | |||
126 | for (const type of types) { | ||
127 | const updatedConstants = this.updatedVideoConstants[type][npmName] | ||
128 | if (!updatedConstants) continue | ||
129 | |||
130 | for (const added of updatedConstants.added) { | ||
131 | delete hash[type][added.key] | ||
132 | } | ||
133 | |||
134 | for (const deleted of updatedConstants.deleted) { | ||
135 | hash[type][deleted.key] = deleted.label | ||
136 | } | ||
137 | |||
138 | delete this.updatedVideoConstants[type][npmName] | ||
139 | } | ||
140 | } | ||
141 | |||
142 | getSettings () { | ||
143 | return this.settings | ||
144 | } | ||
145 | |||
146 | getRouter () { | ||
147 | return this.router | ||
148 | } | ||
149 | |||
150 | getIdAndPassAuths () { | ||
151 | return this.idAndPassAuths | ||
152 | } | ||
153 | |||
154 | getExternalAuths () { | ||
155 | return this.externalAuths | ||
156 | } | ||
157 | |||
158 | getOnSettingsChangedCallbacks () { | ||
159 | return this.onSettingsChangeCallbacks | ||
160 | } | ||
161 | |||
162 | private buildGetRouter () { | ||
163 | return () => this.router | ||
164 | } | ||
165 | |||
166 | private buildRegisterSetting () { | ||
167 | return (options: RegisterServerSettingOptions) => { | ||
168 | this.settings.push(options) | ||
169 | } | ||
170 | } | ||
171 | |||
172 | private buildRegisterHook () { | ||
173 | return (options: RegisterServerHookOptions) => { | ||
174 | if (serverHookObject[options.target] !== true) { | ||
175 | logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName) | ||
176 | return | ||
177 | } | ||
178 | |||
179 | return this.onHookAdded(options) | ||
180 | } | ||
181 | } | ||
182 | |||
183 | private buildRegisterIdAndPassAuth () { | ||
184 | return (options: RegisterServerAuthPassOptions) => { | ||
185 | if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') { | ||
186 | logger.error('Cannot register auth plugin %s: authName, getWeight or login are not valid.', this.npmName, { options }) | ||
187 | return | ||
188 | } | ||
189 | |||
190 | this.idAndPassAuths.push(options) | ||
191 | } | ||
192 | } | ||
193 | |||
194 | private buildRegisterExternalAuth () { | ||
195 | const self = this | ||
196 | |||
197 | return (options: RegisterServerAuthExternalOptions) => { | ||
198 | if (!options.authName || typeof options.authDisplayName !== 'function' || typeof options.onAuthRequest !== 'function') { | ||
199 | logger.error('Cannot register auth plugin %s: authName, authDisplayName or onAuthRequest are not valid.', this.npmName, { options }) | ||
200 | return | ||
201 | } | ||
202 | |||
203 | this.externalAuths.push(options) | ||
204 | |||
205 | return { | ||
206 | userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void { | ||
207 | onExternalUserAuthenticated({ | ||
208 | npmName: self.npmName, | ||
209 | authName: options.authName, | ||
210 | authResult: result | ||
211 | }).catch(err => { | ||
212 | logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err }) | ||
213 | }) | ||
214 | } | ||
215 | } as RegisterServerAuthExternalResult | ||
216 | } | ||
217 | } | ||
218 | |||
219 | private buildUnregisterExternalAuth () { | ||
220 | return (authName: string) => { | ||
221 | this.externalAuths = this.externalAuths.filter(a => a.authName !== authName) | ||
222 | } | ||
223 | } | ||
224 | |||
225 | private buildUnregisterIdAndPassAuth () { | ||
226 | return (authName: string) => { | ||
227 | this.idAndPassAuths = this.idAndPassAuths.filter(a => a.authName !== authName) | ||
228 | } | ||
229 | } | ||
230 | |||
231 | private buildSettingsManager (): PluginSettingsManager { | ||
232 | return { | ||
233 | getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name, this.settings), | ||
234 | |||
235 | getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names, this.settings), | ||
236 | |||
237 | setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value), | ||
238 | |||
239 | onSettingsChange: (cb: (settings: any) => void) => this.onSettingsChangeCallbacks.push(cb) | ||
240 | } | ||
241 | } | ||
242 | |||
243 | private buildStorageManager (): PluginStorageManager { | ||
244 | return { | ||
245 | getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key), | ||
246 | |||
247 | storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data) | ||
248 | } | ||
249 | } | ||
250 | |||
251 | private buildVideoLanguageManager (): PluginVideoLanguageManager { | ||
252 | return { | ||
253 | addLanguage: (key: string, label: string) => { | ||
254 | return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label }) | ||
255 | }, | ||
256 | |||
257 | deleteLanguage: (key: string) => { | ||
258 | return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key }) | ||
259 | } | ||
260 | } | ||
261 | } | ||
262 | |||
263 | private buildVideoCategoryManager (): PluginVideoCategoryManager { | ||
264 | return { | ||
265 | addCategory: (key: number, label: string) => { | ||
266 | return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }) | ||
267 | }, | ||
268 | |||
269 | deleteCategory: (key: number) => { | ||
270 | return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key }) | ||
271 | } | ||
272 | } | ||
273 | } | ||
274 | |||
275 | private buildVideoPrivacyManager (): PluginVideoPrivacyManager { | ||
276 | return { | ||
277 | deletePrivacy: (key: number) => { | ||
278 | return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key }) | ||
279 | } | ||
280 | } | ||
281 | } | ||
282 | |||
283 | private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager { | ||
284 | return { | ||
285 | deletePlaylistPrivacy: (key: number) => { | ||
286 | return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key }) | ||
287 | } | ||
288 | } | ||
289 | } | ||
290 | |||
291 | private buildVideoLicenceManager (): PluginVideoLicenceManager { | ||
292 | return { | ||
293 | addLicence: (key: number, label: string) => { | ||
294 | return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label }) | ||
295 | }, | ||
296 | |||
297 | deleteLicence: (key: number) => { | ||
298 | return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key }) | ||
299 | } | ||
300 | } | ||
301 | } | ||
302 | |||
303 | private addConstant<T extends string | number> (parameters: { | ||
304 | npmName: string | ||
305 | type: AlterableVideoConstant | ||
306 | obj: VideoConstant | ||
307 | key: T | ||
308 | label: string | ||
309 | }) { | ||
310 | const { npmName, type, obj, key, label } = parameters | ||
311 | |||
312 | if (obj[key]) { | ||
313 | logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key) | ||
314 | return false | ||
315 | } | ||
316 | |||
317 | if (!this.updatedVideoConstants[type][npmName]) { | ||
318 | this.updatedVideoConstants[type][npmName] = { | ||
319 | added: [], | ||
320 | deleted: [] | ||
321 | } | ||
322 | } | ||
323 | |||
324 | this.updatedVideoConstants[type][npmName].added.push({ key, label }) | ||
325 | obj[key] = label | ||
326 | |||
327 | return true | ||
328 | } | ||
329 | |||
330 | private deleteConstant<T extends string | number> (parameters: { | ||
331 | npmName: string | ||
332 | type: AlterableVideoConstant | ||
333 | obj: VideoConstant | ||
334 | key: T | ||
335 | }) { | ||
336 | const { npmName, type, obj, key } = parameters | ||
337 | |||
338 | if (!obj[key]) { | ||
339 | logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key) | ||
340 | return false | ||
341 | } | ||
342 | |||
343 | if (!this.updatedVideoConstants[type][npmName]) { | ||
344 | this.updatedVideoConstants[type][npmName] = { | ||
345 | added: [], | ||
346 | deleted: [] | ||
347 | } | ||
348 | } | ||
349 | |||
350 | this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] }) | ||
351 | delete obj[key] | ||
352 | |||
353 | return true | ||
354 | } | ||
355 | } | ||
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index f77d0b62c..b4cd6f8e7 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -6,13 +6,14 @@ import { | |||
6 | CONTACT_FORM_LIFETIME, | 6 | CONTACT_FORM_LIFETIME, |
7 | USER_EMAIL_VERIFY_LIFETIME, | 7 | USER_EMAIL_VERIFY_LIFETIME, |
8 | USER_PASSWORD_RESET_LIFETIME, | 8 | USER_PASSWORD_RESET_LIFETIME, |
9 | USER_PASSWORD_CREATE_LIFETIME, | ||
9 | VIDEO_VIEW_LIFETIME, | 10 | VIDEO_VIEW_LIFETIME, |
10 | WEBSERVER | 11 | WEBSERVER |
11 | } from '../initializers/constants' | 12 | } from '../initializers/constants' |
12 | import { CONFIG } from '../initializers/config' | 13 | import { CONFIG } from '../initializers/config' |
13 | 14 | ||
14 | type CachedRoute = { | 15 | type CachedRoute = { |
15 | body: string, | 16 | body: string |
16 | contentType?: string | 17 | contentType?: string |
17 | statusCode?: string | 18 | statusCode?: string |
18 | } | 19 | } |
@@ -24,7 +25,8 @@ class Redis { | |||
24 | private client: RedisClient | 25 | private client: RedisClient |
25 | private prefix: string | 26 | private prefix: string |
26 | 27 | ||
27 | private constructor () {} | 28 | private constructor () { |
29 | } | ||
28 | 30 | ||
29 | init () { | 31 | init () { |
30 | // Already initialized | 32 | // Already initialized |
@@ -49,9 +51,9 @@ class Redis { | |||
49 | return Object.assign({}, | 51 | return Object.assign({}, |
50 | (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, | 52 | (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, |
51 | (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, | 53 | (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, |
52 | (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) ? | 54 | (CONFIG.REDIS.HOSTNAME && CONFIG.REDIS.PORT) |
53 | { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } : | 55 | ? { host: CONFIG.REDIS.HOSTNAME, port: CONFIG.REDIS.PORT } |
54 | { path: CONFIG.REDIS.SOCKET } | 56 | : { path: CONFIG.REDIS.SOCKET } |
55 | ) | 57 | ) |
56 | } | 58 | } |
57 | 59 | ||
@@ -63,7 +65,7 @@ class Redis { | |||
63 | return this.prefix | 65 | return this.prefix |
64 | } | 66 | } |
65 | 67 | ||
66 | /************* Forgot password *************/ | 68 | /* ************ Forgot password ************ */ |
67 | 69 | ||
68 | async setResetPasswordVerificationString (userId: number) { | 70 | async setResetPasswordVerificationString (userId: number) { |
69 | const generatedString = await generateRandomString(32) | 71 | const generatedString = await generateRandomString(32) |
@@ -73,11 +75,19 @@ class Redis { | |||
73 | return generatedString | 75 | return generatedString |
74 | } | 76 | } |
75 | 77 | ||
78 | async setCreatePasswordVerificationString (userId: number) { | ||
79 | const generatedString = await generateRandomString(32) | ||
80 | |||
81 | await this.setValue(this.generateResetPasswordKey(userId), generatedString, USER_PASSWORD_CREATE_LIFETIME) | ||
82 | |||
83 | return generatedString | ||
84 | } | ||
85 | |||
76 | async getResetPasswordLink (userId: number) { | 86 | async getResetPasswordLink (userId: number) { |
77 | return this.getValue(this.generateResetPasswordKey(userId)) | 87 | return this.getValue(this.generateResetPasswordKey(userId)) |
78 | } | 88 | } |
79 | 89 | ||
80 | /************* Email verification *************/ | 90 | /* ************ Email verification ************ */ |
81 | 91 | ||
82 | async setVerifyEmailVerificationString (userId: number) { | 92 | async setVerifyEmailVerificationString (userId: number) { |
83 | const generatedString = await generateRandomString(32) | 93 | const generatedString = await generateRandomString(32) |
@@ -91,7 +101,7 @@ class Redis { | |||
91 | return this.getValue(this.generateVerifyEmailKey(userId)) | 101 | return this.getValue(this.generateVerifyEmailKey(userId)) |
92 | } | 102 | } |
93 | 103 | ||
94 | /************* Contact form per IP *************/ | 104 | /* ************ Contact form per IP ************ */ |
95 | 105 | ||
96 | async setContactFormIp (ip: string) { | 106 | async setContactFormIp (ip: string) { |
97 | return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME) | 107 | return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME) |
@@ -101,7 +111,7 @@ class Redis { | |||
101 | return this.exists(this.generateContactFormKey(ip)) | 111 | return this.exists(this.generateContactFormKey(ip)) |
102 | } | 112 | } |
103 | 113 | ||
104 | /************* Views per IP *************/ | 114 | /* ************ Views per IP ************ */ |
105 | 115 | ||
106 | setIPVideoView (ip: string, videoUUID: string) { | 116 | setIPVideoView (ip: string, videoUUID: string) { |
107 | return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) | 117 | return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) |
@@ -111,7 +121,7 @@ class Redis { | |||
111 | return this.exists(this.generateViewKey(ip, videoUUID)) | 121 | return this.exists(this.generateViewKey(ip, videoUUID)) |
112 | } | 122 | } |
113 | 123 | ||
114 | /************* API cache *************/ | 124 | /* ************ API cache ************ */ |
115 | 125 | ||
116 | async getCachedRoute (req: express.Request) { | 126 | async getCachedRoute (req: express.Request) { |
117 | const cached = await this.getObject(this.generateCachedRouteKey(req)) | 127 | const cached = await this.getObject(this.generateCachedRouteKey(req)) |
@@ -120,17 +130,17 @@ class Redis { | |||
120 | } | 130 | } |
121 | 131 | ||
122 | setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) { | 132 | setCachedRoute (req: express.Request, body: any, lifetime: number, contentType?: string, statusCode?: number) { |
123 | const cached: CachedRoute = Object.assign({}, { | 133 | const cached: CachedRoute = Object.assign( |
124 | body: body.toString() | 134 | {}, |
125 | }, | 135 | { body: body.toString() }, |
126 | (contentType) ? { contentType } : null, | 136 | (contentType) ? { contentType } : null, |
127 | (statusCode) ? { statusCode: statusCode.toString() } : null | 137 | (statusCode) ? { statusCode: statusCode.toString() } : null |
128 | ) | 138 | ) |
129 | 139 | ||
130 | return this.setObject(this.generateCachedRouteKey(req), cached, lifetime) | 140 | return this.setObject(this.generateCachedRouteKey(req), cached, lifetime) |
131 | } | 141 | } |
132 | 142 | ||
133 | /************* Video views *************/ | 143 | /* ************ Video views ************ */ |
134 | 144 | ||
135 | addVideoView (videoId: number) { | 145 | addVideoView (videoId: number) { |
136 | const keyIncr = this.generateVideoViewKey(videoId) | 146 | const keyIncr = this.generateVideoViewKey(videoId) |
@@ -173,7 +183,7 @@ class Redis { | |||
173 | ]) | 183 | ]) |
174 | } | 184 | } |
175 | 185 | ||
176 | /************* Keys generation *************/ | 186 | /* ************ Keys generation ************ */ |
177 | 187 | ||
178 | generateCachedRouteKey (req: express.Request) { | 188 | generateCachedRouteKey (req: express.Request) { |
179 | return req.method + '-' + req.originalUrl | 189 | return req.method + '-' + req.originalUrl |
@@ -207,7 +217,7 @@ class Redis { | |||
207 | return 'contact-form-' + ip | 217 | return 'contact-form-' + ip |
208 | } | 218 | } |
209 | 219 | ||
210 | /************* Redis helpers *************/ | 220 | /* ************ Redis helpers ************ */ |
211 | 221 | ||
212 | private getValue (key: string) { | 222 | private getValue (key: string) { |
213 | return new Promise<string>((res, rej) => { | 223 | return new Promise<string>((res, rej) => { |
@@ -265,7 +275,7 @@ class Redis { | |||
265 | }) | 275 | }) |
266 | } | 276 | } |
267 | 277 | ||
268 | private setObject (key: string, obj: { [ id: string ]: string }, expirationMilliseconds: number) { | 278 | private setObject (key: string, obj: { [id: string]: string }, expirationMilliseconds: number) { |
269 | return new Promise<void>((res, rej) => { | 279 | return new Promise<void>((res, rej) => { |
270 | this.client.hmset(this.prefix + key, obj, (err, ok) => { | 280 | this.client.hmset(this.prefix + key, obj, (err, ok) => { |
271 | if (err) return rej(err) | 281 | if (err) return rej(err) |
@@ -282,7 +292,7 @@ class Redis { | |||
282 | } | 292 | } |
283 | 293 | ||
284 | private getObject (key: string) { | 294 | private getObject (key: string) { |
285 | return new Promise<{ [ id: string ]: string }>((res, rej) => { | 295 | return new Promise<{ [id: string]: string }>((res, rej) => { |
286 | this.client.hgetall(this.prefix + key, (err, value) => { | 296 | this.client.hgetall(this.prefix + key, (err, value) => { |
287 | if (err) return rej(err) | 297 | if (err) return rej(err) |
288 | 298 | ||
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts index 1b4ecd7c0..361b401a5 100644 --- a/server/lib/redundancy.ts +++ b/server/lib/redundancy.ts | |||
@@ -1,8 +1,12 @@ | |||
1 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | 1 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' |
2 | import { sendUndoCacheFile } from './activitypub/send' | 2 | import { sendUndoCacheFile } from './activitypub/send' |
3 | import { Transaction } from 'sequelize' | 3 | import { Transaction } from 'sequelize' |
4 | import { getServerActor } from '../helpers/utils' | 4 | import { MActorSignature, MVideoRedundancyVideo } from '@server/typings/models' |
5 | import { MVideoRedundancyVideo } from '@server/typings/models' | 5 | import { CONFIG } from '@server/initializers/config' |
6 | import { logger } from '@server/helpers/logger' | ||
7 | import { ActorFollowModel } from '@server/models/activitypub/actor-follow' | ||
8 | import { Activity } from '@shared/models' | ||
9 | import { getServerActor } from '@server/models/application/application' | ||
6 | 10 | ||
7 | async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) { | 11 | async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) { |
8 | const serverActor = await getServerActor() | 12 | const serverActor = await getServerActor() |
@@ -13,17 +17,38 @@ async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t? | |||
13 | await videoRedundancy.destroy({ transaction: t }) | 17 | await videoRedundancy.destroy({ transaction: t }) |
14 | } | 18 | } |
15 | 19 | ||
16 | async function removeRedundancyOf (serverId: number) { | 20 | async function removeRedundanciesOfServer (serverId: number) { |
17 | const videosRedundancy = await VideoRedundancyModel.listLocalOfServer(serverId) | 21 | const redundancies = await VideoRedundancyModel.listLocalOfServer(serverId) |
18 | 22 | ||
19 | for (const redundancy of videosRedundancy) { | 23 | for (const redundancy of redundancies) { |
20 | await removeVideoRedundancy(redundancy) | 24 | await removeVideoRedundancy(redundancy) |
21 | } | 25 | } |
22 | } | 26 | } |
23 | 27 | ||
28 | async function isRedundancyAccepted (activity: Activity, byActor: MActorSignature) { | ||
29 | const configAcceptFrom = CONFIG.REMOTE_REDUNDANCY.VIDEOS.ACCEPT_FROM | ||
30 | if (configAcceptFrom === 'nobody') { | ||
31 | logger.info('Do not accept remote redundancy %s due instance accept policy.', activity.id) | ||
32 | return false | ||
33 | } | ||
34 | |||
35 | if (configAcceptFrom === 'followings') { | ||
36 | const serverActor = await getServerActor() | ||
37 | const allowed = await ActorFollowModel.isFollowedBy(byActor.id, serverActor.id) | ||
38 | |||
39 | if (allowed !== true) { | ||
40 | logger.info('Do not accept remote redundancy %s because actor %s is not followed by our instance.', activity.id, byActor.url) | ||
41 | return false | ||
42 | } | ||
43 | } | ||
44 | |||
45 | return true | ||
46 | } | ||
47 | |||
24 | // --------------------------------------------------------------------------- | 48 | // --------------------------------------------------------------------------- |
25 | 49 | ||
26 | export { | 50 | export { |
27 | removeRedundancyOf, | 51 | isRedundancyAccepted, |
52 | removeRedundanciesOfServer, | ||
28 | removeVideoRedundancy | 53 | removeVideoRedundancy |
29 | } | 54 | } |
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts index dd326bc1e..a57436a45 100644 --- a/server/lib/schedulers/auto-follow-index-instances.ts +++ b/server/lib/schedulers/auto-follow-index-instances.ts | |||
@@ -6,7 +6,7 @@ import { chunk } from 'lodash' | |||
6 | import { doRequest } from '@server/helpers/requests' | 6 | import { doRequest } from '@server/helpers/requests' |
7 | import { ActorFollowModel } from '@server/models/activitypub/actor-follow' | 7 | import { ActorFollowModel } from '@server/models/activitypub/actor-follow' |
8 | import { JobQueue } from '@server/lib/job-queue' | 8 | import { JobQueue } from '@server/lib/job-queue' |
9 | import { getServerActor } from '@server/helpers/utils' | 9 | import { getServerActor } from '@server/models/application/application' |
10 | 10 | ||
11 | export class AutoFollowIndexInstances extends AbstractScheduler { | 11 | export class AutoFollowIndexInstances extends AbstractScheduler { |
12 | 12 | ||
@@ -41,7 +41,11 @@ export class AutoFollowIndexInstances extends AbstractScheduler { | |||
41 | 41 | ||
42 | this.lastCheck = new Date() | 42 | this.lastCheck = new Date() |
43 | 43 | ||
44 | const { body } = await doRequest({ uri, qs, json: true }) | 44 | const { body } = await doRequest<any>({ uri, qs, json: true }) |
45 | if (!body.data || Array.isArray(body.data) === false) { | ||
46 | logger.error('Cannot auto follow instances of index %s: bad URL format. Please check the auto follow URL.', indexUrl) | ||
47 | return | ||
48 | } | ||
45 | 49 | ||
46 | const hosts: string[] = body.data.map(o => o.host) | 50 | const hosts: string[] = body.data.map(o => o.host) |
47 | const chunks = chunk(hosts, 20) | 51 | const chunks = chunk(hosts, 20) |
@@ -57,8 +61,7 @@ export class AutoFollowIndexInstances extends AbstractScheduler { | |||
57 | isAutoFollow: true | 61 | isAutoFollow: true |
58 | } | 62 | } |
59 | 63 | ||
60 | await JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | 64 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) |
61 | .catch(err => logger.error('Cannot create follow job for %s.', unfollowedHost, err)) | ||
62 | } | 65 | } |
63 | } | 66 | } |
64 | 67 | ||
diff --git a/server/lib/schedulers/plugins-check-scheduler.ts b/server/lib/schedulers/plugins-check-scheduler.ts index 7ff41e639..014993e94 100644 --- a/server/lib/schedulers/plugins-check-scheduler.ts +++ b/server/lib/schedulers/plugins-check-scheduler.ts | |||
@@ -43,7 +43,7 @@ export class PluginsCheckScheduler extends AbstractScheduler { | |||
43 | const results = await getLatestPluginsVersion(npmNames) | 43 | const results = await getLatestPluginsVersion(npmNames) |
44 | 44 | ||
45 | for (const result of results) { | 45 | for (const result of results) { |
46 | const plugin = pluginIndex[ result.npmName ] | 46 | const plugin = pluginIndex[result.npmName] |
47 | if (!result.latestVersion) continue | 47 | if (!result.latestVersion) continue |
48 | 48 | ||
49 | if ( | 49 | if ( |
diff --git a/server/lib/schedulers/remove-old-views-scheduler.ts b/server/lib/schedulers/remove-old-views-scheduler.ts index 39fbb9163..5ae87fe50 100644 --- a/server/lib/schedulers/remove-old-views-scheduler.ts +++ b/server/lib/schedulers/remove-old-views-scheduler.ts | |||
@@ -1,9 +1,7 @@ | |||
1 | import { logger } from '../../helpers/logger' | 1 | import { logger } from '../../helpers/logger' |
2 | import { AbstractScheduler } from './abstract-scheduler' | 2 | import { AbstractScheduler } from './abstract-scheduler' |
3 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | 3 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
4 | import { UserVideoHistoryModel } from '../../models/account/user-video-history' | ||
5 | import { CONFIG } from '../../initializers/config' | 4 | import { CONFIG } from '../../initializers/config' |
6 | import { isTestInstance } from '../../helpers/core-utils' | ||
7 | import { VideoViewModel } from '../../models/video/video-views' | 5 | import { VideoViewModel } from '../../models/video/video-views' |
8 | 6 | ||
9 | export class RemoveOldViewsScheduler extends AbstractScheduler { | 7 | export class RemoveOldViewsScheduler extends AbstractScheduler { |
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts index 350a335d3..d32c1c068 100644 --- a/server/lib/schedulers/update-videos-scheduler.ts +++ b/server/lib/schedulers/update-videos-scheduler.ts | |||
@@ -2,9 +2,8 @@ import { logger } from '../../helpers/logger' | |||
2 | import { AbstractScheduler } from './abstract-scheduler' | 2 | import { AbstractScheduler } from './abstract-scheduler' |
3 | import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-update' | 3 | import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-update' |
4 | import { retryTransactionWrapper } from '../../helpers/database-utils' | 4 | import { retryTransactionWrapper } from '../../helpers/database-utils' |
5 | import { federateVideoIfNeeded } from '../activitypub' | 5 | import { federateVideoIfNeeded } from '../activitypub/videos' |
6 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | 6 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
7 | import { VideoPrivacy } from '../../../shared/models/videos' | ||
8 | import { Notifier } from '../notifier' | 7 | import { Notifier } from '../notifier' |
9 | import { sequelizeTypescript } from '../../initializers/database' | 8 | import { sequelizeTypescript } from '../../initializers/database' |
10 | import { MVideoFullLight } from '@server/typings/models' | 9 | import { MVideoFullLight } from '@server/typings/models' |
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index c1c91b656..8da9d52b5 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -1,16 +1,15 @@ | |||
1 | import { AbstractScheduler } from './abstract-scheduler' | 1 | import { AbstractScheduler } from './abstract-scheduler' |
2 | import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants' | 2 | import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { VideosRedundancy } from '../../../shared/models/redundancy' | 4 | import { VideosRedundancyStrategy } from '../../../shared/models/redundancy' |
5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
6 | import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent' | 6 | import { downloadWebTorrentVideo, generateMagnetUri } from '../../helpers/webtorrent' |
7 | import { join } from 'path' | 7 | import { join } from 'path' |
8 | import { move } from 'fs-extra' | 8 | import { move } from 'fs-extra' |
9 | import { getServerActor } from '../../helpers/utils' | ||
10 | import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' | 9 | import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' |
11 | import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' | 10 | import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' |
12 | import { removeVideoRedundancy } from '../redundancy' | 11 | import { removeVideoRedundancy } from '../redundancy' |
13 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' | 12 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub/videos' |
14 | import { downloadPlaylistSegments } from '../hls' | 13 | import { downloadPlaylistSegments } from '../hls' |
15 | import { CONFIG } from '../../initializers/config' | 14 | import { CONFIG } from '../../initializers/config' |
16 | import { | 15 | import { |
@@ -25,11 +24,13 @@ import { | |||
25 | MVideoWithAllFiles | 24 | MVideoWithAllFiles |
26 | } from '@server/typings/models' | 25 | } from '@server/typings/models' |
27 | import { getVideoFilename } from '../video-paths' | 26 | import { getVideoFilename } from '../video-paths' |
27 | import { VideoModel } from '@server/models/video/video' | ||
28 | import { getServerActor } from '@server/models/application/application' | ||
28 | 29 | ||
29 | type CandidateToDuplicate = { | 30 | type CandidateToDuplicate = { |
30 | redundancy: VideosRedundancy, | 31 | redundancy: VideosRedundancyStrategy |
31 | video: MVideoWithAllFiles, | 32 | video: MVideoWithAllFiles |
32 | files: MVideoFile[], | 33 | files: MVideoFile[] |
33 | streamingPlaylists: MStreamingPlaylistFiles[] | 34 | streamingPlaylists: MStreamingPlaylistFiles[] |
34 | } | 35 | } |
35 | 36 | ||
@@ -41,7 +42,7 @@ function isMVideoRedundancyFileVideo ( | |||
41 | 42 | ||
42 | export class VideosRedundancyScheduler extends AbstractScheduler { | 43 | export class VideosRedundancyScheduler extends AbstractScheduler { |
43 | 44 | ||
44 | private static instance: AbstractScheduler | 45 | private static instance: VideosRedundancyScheduler |
45 | 46 | ||
46 | protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL | 47 | protected schedulerIntervalMs = CONFIG.REDUNDANCY.VIDEOS.CHECK_INTERVAL |
47 | 48 | ||
@@ -49,6 +50,22 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
49 | super() | 50 | super() |
50 | } | 51 | } |
51 | 52 | ||
53 | async createManualRedundancy (videoId: number) { | ||
54 | const videoToDuplicate = await VideoModel.loadWithFiles(videoId) | ||
55 | |||
56 | if (!videoToDuplicate) { | ||
57 | logger.warn('Video to manually duplicate %d does not exist anymore.', videoId) | ||
58 | return | ||
59 | } | ||
60 | |||
61 | return this.createVideoRedundancies({ | ||
62 | video: videoToDuplicate, | ||
63 | redundancy: null, | ||
64 | files: videoToDuplicate.VideoFiles, | ||
65 | streamingPlaylists: videoToDuplicate.VideoStreamingPlaylists | ||
66 | }) | ||
67 | } | ||
68 | |||
52 | protected async internalExecute () { | 69 | protected async internalExecute () { |
53 | for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) { | 70 | for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) { |
54 | logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy) | 71 | logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy) |
@@ -94,7 +111,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
94 | for (const redundancyModel of expired) { | 111 | for (const redundancyModel of expired) { |
95 | try { | 112 | try { |
96 | const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) | 113 | const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) |
97 | const candidate = { | 114 | const candidate: CandidateToDuplicate = { |
98 | redundancy: redundancyConfig, | 115 | redundancy: redundancyConfig, |
99 | video: null, | 116 | video: null, |
100 | files: [], | 117 | files: [], |
@@ -140,7 +157,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
140 | } | 157 | } |
141 | } | 158 | } |
142 | 159 | ||
143 | private findVideoToDuplicate (cache: VideosRedundancy) { | 160 | private findVideoToDuplicate (cache: VideosRedundancyStrategy) { |
144 | if (cache.strategy === 'most-views') { | 161 | if (cache.strategy === 'most-views') { |
145 | return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR) | 162 | return VideoRedundancyModel.findMostViewToDuplicate(REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR) |
146 | } | 163 | } |
@@ -187,13 +204,21 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
187 | } | 204 | } |
188 | } | 205 | } |
189 | 206 | ||
190 | private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) { | 207 | private async createVideoFileRedundancy (redundancy: VideosRedundancyStrategy | null, video: MVideoAccountLight, fileArg: MVideoFile) { |
208 | let strategy = 'manual' | ||
209 | let expiresOn: Date = null | ||
210 | |||
211 | if (redundancy) { | ||
212 | strategy = redundancy.strategy | ||
213 | expiresOn = this.buildNewExpiration(redundancy.minLifetime) | ||
214 | } | ||
215 | |||
191 | const file = fileArg as MVideoFileVideo | 216 | const file = fileArg as MVideoFileVideo |
192 | file.Video = video | 217 | file.Video = video |
193 | 218 | ||
194 | const serverActor = await getServerActor() | 219 | const serverActor = await getServerActor() |
195 | 220 | ||
196 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, redundancy.strategy) | 221 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, strategy) |
197 | 222 | ||
198 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 223 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
199 | const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs) | 224 | const magnetUri = generateMagnetUri(video, file, baseUrlHttp, baseUrlWs) |
@@ -204,10 +229,10 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
204 | await move(tmpPath, destPath, { overwrite: true }) | 229 | await move(tmpPath, destPath, { overwrite: true }) |
205 | 230 | ||
206 | const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ | 231 | const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ |
207 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 232 | expiresOn, |
208 | url: getVideoCacheFileActivityPubUrl(file), | 233 | url: getVideoCacheFileActivityPubUrl(file), |
209 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), | 234 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), |
210 | strategy: redundancy.strategy, | 235 | strategy, |
211 | videoFileId: file.id, | 236 | videoFileId: file.id, |
212 | actorId: serverActor.id | 237 | actorId: serverActor.id |
213 | }) | 238 | }) |
@@ -220,25 +245,33 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
220 | } | 245 | } |
221 | 246 | ||
222 | private async createStreamingPlaylistRedundancy ( | 247 | private async createStreamingPlaylistRedundancy ( |
223 | redundancy: VideosRedundancy, | 248 | redundancy: VideosRedundancyStrategy, |
224 | video: MVideoAccountLight, | 249 | video: MVideoAccountLight, |
225 | playlistArg: MStreamingPlaylist | 250 | playlistArg: MStreamingPlaylist |
226 | ) { | 251 | ) { |
252 | let strategy = 'manual' | ||
253 | let expiresOn: Date = null | ||
254 | |||
255 | if (redundancy) { | ||
256 | strategy = redundancy.strategy | ||
257 | expiresOn = this.buildNewExpiration(redundancy.minLifetime) | ||
258 | } | ||
259 | |||
227 | const playlist = playlistArg as MStreamingPlaylistVideo | 260 | const playlist = playlistArg as MStreamingPlaylistVideo |
228 | playlist.Video = video | 261 | playlist.Video = video |
229 | 262 | ||
230 | const serverActor = await getServerActor() | 263 | const serverActor = await getServerActor() |
231 | 264 | ||
232 | logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, redundancy.strategy) | 265 | logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, strategy) |
233 | 266 | ||
234 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) | 267 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) |
235 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) | 268 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) |
236 | 269 | ||
237 | const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ | 270 | const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ |
238 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 271 | expiresOn, |
239 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), | 272 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), |
240 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), | 273 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), |
241 | strategy: redundancy.strategy, | 274 | strategy, |
242 | videoStreamingPlaylistId: playlist.id, | 275 | videoStreamingPlaylistId: playlist.id, |
243 | actorId: serverActor.id | 276 | actorId: serverActor.id |
244 | }) | 277 | }) |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index a99f71629..8dbd41771 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -69,7 +69,7 @@ function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, | |||
69 | function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { | 69 | function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { |
70 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 70 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
71 | 71 | ||
72 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() | 72 | const thumbnail = existingThumbnail || new ThumbnailModel() |
73 | 73 | ||
74 | thumbnail.filename = filename | 74 | thumbnail.filename = filename |
75 | thumbnail.height = height | 75 | thumbnail.height = height |
@@ -142,18 +142,18 @@ function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, si | |||
142 | } | 142 | } |
143 | 143 | ||
144 | async function createThumbnailFromFunction (parameters: { | 144 | async function createThumbnailFromFunction (parameters: { |
145 | thumbnailCreator: () => Promise<any>, | 145 | thumbnailCreator: () => Promise<any> |
146 | filename: string, | 146 | filename: string |
147 | height: number, | 147 | height: number |
148 | width: number, | 148 | width: number |
149 | type: ThumbnailType, | 149 | type: ThumbnailType |
150 | automaticallyGenerated?: boolean, | 150 | automaticallyGenerated?: boolean |
151 | fileUrl?: string, | 151 | fileUrl?: string |
152 | existingThumbnail?: MThumbnail | 152 | existingThumbnail?: MThumbnail |
153 | }) { | 153 | }) { |
154 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters | 154 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters |
155 | 155 | ||
156 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() | 156 | const thumbnail = existingThumbnail || new ThumbnailModel() |
157 | 157 | ||
158 | thumbnail.filename = filename | 158 | thumbnail.filename = filename |
159 | thumbnail.height = height | 159 | thumbnail.height = height |
diff --git a/server/lib/user.ts b/server/lib/user.ts index c45438d95..8b447583e 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as uuidv4 from 'uuid/v4' | 1 | import { v4 as uuidv4 } from 'uuid' |
2 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 2 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
3 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' | 3 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' |
4 | import { AccountModel } from '../models/account/account' | 4 | import { AccountModel } from '../models/account/account' |
5 | import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' | 5 | import { buildActorInstance, setAsyncActorKeys } from './activitypub/actor' |
6 | import { createLocalVideoChannel } from './video-channel' | 6 | import { createLocalVideoChannel } from './video-channel' |
7 | import { ActorModel } from '../models/activitypub/actor' | 7 | import { ActorModel } from '../models/activitypub/actor' |
8 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | 8 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' |
@@ -14,13 +14,14 @@ import { Redis } from './redis' | |||
14 | import { Emailer } from './emailer' | 14 | import { Emailer } from './emailer' |
15 | import { MAccountDefault, MActorDefault, MChannelActor } from '../typings/models' | 15 | import { MAccountDefault, MActorDefault, MChannelActor } from '../typings/models' |
16 | import { MUser, MUserDefault, MUserId } from '../typings/models/user' | 16 | import { MUser, MUserDefault, MUserId } from '../typings/models/user' |
17 | import { getAccountActivityPubUrl } from './activitypub/url' | ||
17 | 18 | ||
18 | type ChannelNames = { name: string, displayName: string } | 19 | type ChannelNames = { name: string, displayName: string } |
19 | 20 | ||
20 | async function createUserAccountAndChannelAndPlaylist (parameters: { | 21 | async function createUserAccountAndChannelAndPlaylist (parameters: { |
21 | userToCreate: MUser, | 22 | userToCreate: MUser |
22 | userDisplayName?: string, | 23 | userDisplayName?: string |
23 | channelNames?: ChannelNames, | 24 | channelNames?: ChannelNames |
24 | validateUser?: boolean | 25 | validateUser?: boolean |
25 | }): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { | 26 | }): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { |
26 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters | 27 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters |
@@ -63,11 +64,11 @@ async function createUserAccountAndChannelAndPlaylist (parameters: { | |||
63 | } | 64 | } |
64 | 65 | ||
65 | async function createLocalAccountWithoutKeys (parameters: { | 66 | async function createLocalAccountWithoutKeys (parameters: { |
66 | name: string, | 67 | name: string |
67 | displayName?: string, | 68 | displayName?: string |
68 | userId: number | null, | 69 | userId: number | null |
69 | applicationId: number | null, | 70 | applicationId: number | null |
70 | t: Transaction | undefined, | 71 | t: Transaction | undefined |
71 | type?: ActivityPubActorType | 72 | type?: ActivityPubActorType |
72 | }) { | 73 | }) { |
73 | const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters | 74 | const { name, displayName, userId, applicationId, t, type = 'Person' } = parameters |
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts index 1dd45b76d..bd60c6201 100644 --- a/server/lib/video-blacklist.ts +++ b/server/lib/video-blacklist.ts | |||
@@ -1,23 +1,33 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { sequelizeTypescript } from '@server/initializers/database' | ||
3 | import { | ||
4 | MUser, | ||
5 | MVideoAccountLight, | ||
6 | MVideoBlacklist, | ||
7 | MVideoBlacklistVideo, | ||
8 | MVideoFullLight, | ||
9 | MVideoWithBlacklistLight | ||
10 | } from '@server/typings/models' | ||
11 | import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../shared/models' | ||
12 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' | ||
13 | import { logger } from '../helpers/logger' | ||
2 | import { CONFIG } from '../initializers/config' | 14 | import { CONFIG } from '../initializers/config' |
3 | import { UserRight, VideoBlacklistType } from '../../shared/models' | ||
4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 15 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
5 | import { logger } from '../helpers/logger' | 16 | import { sendDeleteVideo } from './activitypub/send' |
6 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' | 17 | import { federateVideoIfNeeded } from './activitypub/videos' |
7 | import { Hooks } from './plugins/hooks' | ||
8 | import { Notifier } from './notifier' | 18 | import { Notifier } from './notifier' |
9 | import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models' | 19 | import { Hooks } from './plugins/hooks' |
10 | 20 | ||
11 | async function autoBlacklistVideoIfNeeded (parameters: { | 21 | async function autoBlacklistVideoIfNeeded (parameters: { |
12 | video: MVideoWithBlacklistLight, | 22 | video: MVideoWithBlacklistLight |
13 | user?: MUser, | 23 | user?: MUser |
14 | isRemote: boolean, | 24 | isRemote: boolean |
15 | isNew: boolean, | 25 | isNew: boolean |
16 | notify?: boolean, | 26 | notify?: boolean |
17 | transaction?: Transaction | 27 | transaction?: Transaction |
18 | }) { | 28 | }) { |
19 | const { video, user, isRemote, isNew, notify = true, transaction } = parameters | 29 | const { video, user, isRemote, isNew, notify = true, transaction } = parameters |
20 | const doAutoBlacklist = await Hooks.wrapPromiseFun( | 30 | const doAutoBlacklist = await Hooks.wrapFun( |
21 | autoBlacklistNeeded, | 31 | autoBlacklistNeeded, |
22 | { video, user, isRemote, isNew }, | 32 | { video, user, isRemote, isNew }, |
23 | 'filter:video.auto-blacklist.result' | 33 | 'filter:video.auto-blacklist.result' |
@@ -49,10 +59,64 @@ async function autoBlacklistVideoIfNeeded (parameters: { | |||
49 | return true | 59 | return true |
50 | } | 60 | } |
51 | 61 | ||
52 | async function autoBlacklistNeeded (parameters: { | 62 | async function blacklistVideo (videoInstance: MVideoAccountLight, options: VideoBlacklistCreate) { |
53 | video: MVideoWithBlacklistLight, | 63 | const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create({ |
54 | isRemote: boolean, | 64 | videoId: videoInstance.id, |
55 | isNew: boolean, | 65 | unfederated: options.unfederate === true, |
66 | reason: options.reason, | ||
67 | type: VideoBlacklistType.MANUAL | ||
68 | } | ||
69 | ) | ||
70 | blacklist.Video = videoInstance | ||
71 | |||
72 | if (options.unfederate === true) { | ||
73 | await sendDeleteVideo(videoInstance, undefined) | ||
74 | } | ||
75 | |||
76 | Notifier.Instance.notifyOnVideoBlacklist(blacklist) | ||
77 | } | ||
78 | |||
79 | async function unblacklistVideo (videoBlacklist: MVideoBlacklist, video: MVideoFullLight) { | ||
80 | const videoBlacklistType = await sequelizeTypescript.transaction(async t => { | ||
81 | const unfederated = videoBlacklist.unfederated | ||
82 | const videoBlacklistType = videoBlacklist.type | ||
83 | |||
84 | await videoBlacklist.destroy({ transaction: t }) | ||
85 | video.VideoBlacklist = undefined | ||
86 | |||
87 | // Re federate the video | ||
88 | if (unfederated === true) { | ||
89 | await federateVideoIfNeeded(video, true, t) | ||
90 | } | ||
91 | |||
92 | return videoBlacklistType | ||
93 | }) | ||
94 | |||
95 | Notifier.Instance.notifyOnVideoUnblacklist(video) | ||
96 | |||
97 | if (videoBlacklistType === VideoBlacklistType.AUTO_BEFORE_PUBLISHED) { | ||
98 | Notifier.Instance.notifyOnVideoPublishedAfterRemovedFromAutoBlacklist(video) | ||
99 | |||
100 | // Delete on object so new video notifications will send | ||
101 | delete video.VideoBlacklist | ||
102 | Notifier.Instance.notifyOnNewVideoIfNeeded(video) | ||
103 | } | ||
104 | } | ||
105 | |||
106 | // --------------------------------------------------------------------------- | ||
107 | |||
108 | export { | ||
109 | autoBlacklistVideoIfNeeded, | ||
110 | blacklistVideo, | ||
111 | unblacklistVideo | ||
112 | } | ||
113 | |||
114 | // --------------------------------------------------------------------------- | ||
115 | |||
116 | function autoBlacklistNeeded (parameters: { | ||
117 | video: MVideoWithBlacklistLight | ||
118 | isRemote: boolean | ||
119 | isNew: boolean | ||
56 | user?: MUser | 120 | user?: MUser |
57 | }) { | 121 | }) { |
58 | const { user, video, isRemote, isNew } = parameters | 122 | const { user, video, isRemote, isNew } = parameters |
@@ -66,9 +130,3 @@ async function autoBlacklistNeeded (parameters: { | |||
66 | 130 | ||
67 | return true | 131 | return true |
68 | } | 132 | } |
69 | |||
70 | // --------------------------------------------------------------------------- | ||
71 | |||
72 | export { | ||
73 | autoBlacklistVideoIfNeeded | ||
74 | } | ||
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index 41eab456b..102c1088d 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts | |||
@@ -1,13 +1,14 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as uuidv4 from 'uuid/v4' | 2 | import { v4 as uuidv4 } from 'uuid' |
3 | import { VideoChannelCreate } from '../../shared/models' | 3 | import { VideoChannelCreate } from '../../shared/models' |
4 | import { VideoChannelModel } from '../models/video/video-channel' | 4 | import { VideoChannelModel } from '../models/video/video-channel' |
5 | import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' | 5 | import { buildActorInstance } from './activitypub/actor' |
6 | import { VideoModel } from '../models/video/video' | 6 | import { VideoModel } from '../models/video/video' |
7 | import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' | 7 | import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' |
8 | import { getVideoChannelActivityPubUrl } from './activitypub/url' | ||
9 | import { federateVideoIfNeeded } from './activitypub/videos' | ||
8 | 10 | ||
9 | type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & | 11 | type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & { Account?: T } |
10 | { Account?: T } | ||
11 | 12 | ||
12 | async function createLocalVideoChannel <T extends MAccountId> ( | 13 | async function createLocalVideoChannel <T extends MAccountId> ( |
13 | videoChannelInfo: VideoChannelCreate, | 14 | videoChannelInfo: VideoChannelCreate, |
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index b8074e6d2..516c912a9 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts | |||
@@ -2,14 +2,14 @@ import * as Sequelize from 'sequelize' | |||
2 | import { ResultList } from '../../shared/models' | 2 | import { ResultList } from '../../shared/models' |
3 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' | 3 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' |
4 | import { VideoCommentModel } from '../models/video/video-comment' | 4 | import { VideoCommentModel } from '../models/video/video-comment' |
5 | import { getVideoCommentActivityPubUrl } from './activitypub' | 5 | import { getVideoCommentActivityPubUrl } from './activitypub/url' |
6 | import { sendCreateVideoComment } from './activitypub/send' | 6 | import { sendCreateVideoComment } from './activitypub/send' |
7 | import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' | 7 | import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' |
8 | 8 | ||
9 | async function createVideoComment (obj: { | 9 | async function createVideoComment (obj: { |
10 | text: string, | 10 | text: string |
11 | inReplyToComment: MComment | null, | 11 | inReplyToComment: MComment | null |
12 | video: MVideoFullLight, | 12 | video: MVideoFullLight |
13 | account: MAccountDefault | 13 | account: MAccountDefault |
14 | }, t: Sequelize.Transaction) { | 14 | }, t: Sequelize.Transaction) { |
15 | let originCommentId: number | null = null | 15 | let originCommentId: number | null = null |
diff --git a/server/lib/video-paths.ts b/server/lib/video-paths.ts index fe0a004e4..05aaca8af 100644 --- a/server/lib/video-paths.ts +++ b/server/lib/video-paths.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/typings/models' | 1 | import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo, MVideoFile, MVideoUUID } from '@server/typings/models' |
2 | import { extractVideo } from './videos' | ||
3 | import { join } from 'path' | 2 | import { join } from 'path' |
4 | import { CONFIG } from '@server/initializers/config' | 3 | import { CONFIG } from '@server/initializers/config' |
5 | import { HLS_REDUNDANCY_DIRECTORY, HLS_STREAMING_PLAYLIST_DIRECTORY } from '@server/initializers/constants' | 4 | import { HLS_REDUNDANCY_DIRECTORY, HLS_STREAMING_PLAYLIST_DIRECTORY } from '@server/initializers/constants' |
5 | import { extractVideo } from '@server/helpers/video' | ||
6 | 6 | ||
7 | // ################## Video file name ################## | 7 | // ################## Video file name ################## |
8 | 8 | ||
diff --git a/server/lib/video-playlist.ts b/server/lib/video-playlist.ts index 29b70cfda..75fbd6896 100644 --- a/server/lib/video-playlist.ts +++ b/server/lib/video-playlist.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 2 | import { VideoPlaylistModel } from '../models/video/video-playlist' |
3 | import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' | 3 | import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' |
4 | import { getVideoPlaylistActivityPubUrl } from './activitypub' | 4 | import { getVideoPlaylistActivityPubUrl } from './activitypub/url' |
5 | import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' | 5 | import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' |
6 | import { MAccount } from '../typings/models' | 6 | import { MAccount } from '../typings/models' |
7 | import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist' | 7 | import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist' |
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index 0d5b3ae39..dcda82e0a 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -3,6 +3,7 @@ import { basename, extname as extnameUtil, join } from 'path' | |||
3 | import { | 3 | import { |
4 | canDoQuickTranscode, | 4 | canDoQuickTranscode, |
5 | getDurationFromVideoFile, | 5 | getDurationFromVideoFile, |
6 | getMetadataFromFile, | ||
6 | getVideoFileFPS, | 7 | getVideoFileFPS, |
7 | transcode, | 8 | transcode, |
8 | TranscodeOptions, | 9 | TranscodeOptions, |
@@ -202,10 +203,11 @@ async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoReso | |||
202 | 203 | ||
203 | newVideoFile.size = stats.size | 204 | newVideoFile.size = stats.size |
204 | newVideoFile.fps = await getVideoFileFPS(videoFilePath) | 205 | newVideoFile.fps = await getVideoFileFPS(videoFilePath) |
206 | newVideoFile.metadata = await getMetadataFromFile(videoFilePath) | ||
205 | 207 | ||
206 | await createTorrentAndSetInfoHash(videoStreamingPlaylist, newVideoFile) | 208 | await createTorrentAndSetInfoHash(videoStreamingPlaylist, newVideoFile) |
207 | 209 | ||
208 | await newVideoFile.save() | 210 | await VideoFileModel.customUpsert(newVideoFile, 'streaming-playlist', undefined) |
209 | videoStreamingPlaylist.VideoFiles = await videoStreamingPlaylist.$get('VideoFiles') | 211 | videoStreamingPlaylist.VideoFiles = await videoStreamingPlaylist.$get('VideoFiles') |
210 | 212 | ||
211 | video.setHLSPlaylist(videoStreamingPlaylist) | 213 | video.setHLSPlaylist(videoStreamingPlaylist) |
@@ -230,11 +232,13 @@ export { | |||
230 | async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) { | 232 | async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) { |
231 | const stats = await stat(transcodingPath) | 233 | const stats = await stat(transcodingPath) |
232 | const fps = await getVideoFileFPS(transcodingPath) | 234 | const fps = await getVideoFileFPS(transcodingPath) |
235 | const metadata = await getMetadataFromFile(transcodingPath) | ||
233 | 236 | ||
234 | await move(transcodingPath, outputPath) | 237 | await move(transcodingPath, outputPath) |
235 | 238 | ||
236 | videoFile.size = stats.size | 239 | videoFile.size = stats.size |
237 | videoFile.fps = fps | 240 | videoFile.fps = fps |
241 | videoFile.metadata = metadata | ||
238 | 242 | ||
239 | await createTorrentAndSetInfoHash(video, videoFile) | 243 | await createTorrentAndSetInfoHash(video, videoFile) |
240 | 244 | ||
diff --git a/server/lib/videos.ts b/server/lib/videos.ts deleted file mode 100644 index 22e9afbf9..000000000 --- a/server/lib/videos.ts +++ /dev/null | |||
@@ -1,11 +0,0 @@ | |||
1 | import { isStreamingPlaylist, MStreamingPlaylistVideo, MVideo } from '@server/typings/models' | ||
2 | |||
3 | function extractVideo (videoOrPlaylist: MVideo | MStreamingPlaylistVideo) { | ||
4 | return isStreamingPlaylist(videoOrPlaylist) | ||
5 | ? videoOrPlaylist.Video | ||
6 | : videoOrPlaylist | ||
7 | } | ||
8 | |||
9 | export { | ||
10 | extractVideo | ||
11 | } | ||
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index c6d8466ac..580606a68 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import { NextFunction, Request, Response } from 'express' | 1 | import { NextFunction, Request, Response } from 'express' |
2 | import { ActivityPubSignature } from '../../shared' | 2 | import { ActivityDelete, ActivityPubSignature } from '../../shared' |
3 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
4 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' | 4 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' |
5 | import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' | 5 | import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' |
6 | import { getOrCreateActorAndServerAndModel } from '../lib/activitypub' | 6 | import { getOrCreateActorAndServerAndModel } from '../lib/activitypub/actor' |
7 | import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger' | 7 | import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger' |
8 | import { isActorDeleteActivityValid } from '@server/helpers/custom-validators/activitypub/actor' | ||
9 | import { getAPId } from '@server/helpers/activitypub' | ||
8 | 10 | ||
9 | async function checkSignature (req: Request, res: Response, next: NextFunction) { | 11 | async function checkSignature (req: Request, res: Response, next: NextFunction) { |
10 | try { | 12 | try { |
@@ -15,7 +17,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction) | |||
15 | 17 | ||
16 | // Forwarded activity | 18 | // Forwarded activity |
17 | const bodyActor = req.body.actor | 19 | const bodyActor = req.body.actor |
18 | const bodyActorId = bodyActor && bodyActor.id ? bodyActor.id : bodyActor | 20 | const bodyActorId = getAPId(bodyActor) |
19 | if (bodyActorId && bodyActorId !== actor.url) { | 21 | if (bodyActorId && bodyActorId !== actor.url) { |
20 | const jsonLDSignatureChecked = await checkJsonLDSignature(req, res) | 22 | const jsonLDSignatureChecked = await checkJsonLDSignature(req, res) |
21 | if (jsonLDSignatureChecked !== true) return | 23 | if (jsonLDSignatureChecked !== true) return |
@@ -23,14 +25,20 @@ async function checkSignature (req: Request, res: Response, next: NextFunction) | |||
23 | 25 | ||
24 | return next() | 26 | return next() |
25 | } catch (err) { | 27 | } catch (err) { |
26 | logger.error('Error in ActivityPub signature checker.', err) | 28 | const activity: ActivityDelete = req.body |
29 | if (isActorDeleteActivityValid(activity) && activity.object === activity.actor) { | ||
30 | logger.debug('Handling signature error on actor delete activity', { err }) | ||
31 | return res.sendStatus(204) | ||
32 | } | ||
33 | |||
34 | logger.warn('Error in ActivityPub signature checker.', { err }) | ||
27 | return res.sendStatus(403) | 35 | return res.sendStatus(403) |
28 | } | 36 | } |
29 | } | 37 | } |
30 | 38 | ||
31 | function executeIfActivityPub (req: Request, res: Response, next: NextFunction) { | 39 | function executeIfActivityPub (req: Request, res: Response, next: NextFunction) { |
32 | const accepted = req.accepts(ACCEPT_HEADERS) | 40 | const accepted = req.accepts(ACCEPT_HEADERS) |
33 | if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.indexOf(accepted) === -1) { | 41 | if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.includes(accepted) === false) { |
34 | // Bypass this route | 42 | // Bypass this route |
35 | return next('route') | 43 | return next('route') |
36 | } | 44 | } |
diff --git a/server/middlewares/csp.ts b/server/middlewares/csp.ts index d11d70790..f5de69603 100644 --- a/server/middlewares/csp.ts +++ b/server/middlewares/csp.ts | |||
@@ -3,20 +3,20 @@ import { CONFIG } from '../initializers/config' | |||
3 | 3 | ||
4 | const baseDirectives = Object.assign({}, | 4 | const baseDirectives = Object.assign({}, |
5 | { | 5 | { |
6 | defaultSrc: ["'none'"], // by default, not specifying default-src = '*' | 6 | defaultSrc: [ '\'none\'' ], // by default, not specifying default-src = '*' |
7 | connectSrc: ['*', 'data:'], | 7 | connectSrc: [ '*', 'data:' ], |
8 | mediaSrc: ["'self'", 'https:', 'blob:'], | 8 | mediaSrc: [ '\'self\'', 'https:', 'blob:' ], |
9 | fontSrc: ["'self'", 'data:'], | 9 | fontSrc: [ '\'self\'', 'data:' ], |
10 | imgSrc: ["'self'", 'data:', 'blob:'], | 10 | imgSrc: [ '\'self\'', 'data:', 'blob:' ], |
11 | scriptSrc: ["'self' 'unsafe-inline' 'unsafe-eval'", 'blob:'], | 11 | scriptSrc: [ '\'self\' \'unsafe-inline\' \'unsafe-eval\'', 'blob:' ], |
12 | styleSrc: ["'self' 'unsafe-inline'"], | 12 | styleSrc: [ '\'self\' \'unsafe-inline\'' ], |
13 | objectSrc: ["'none'"], // only define to allow plugins, else let defaultSrc 'none' block it | 13 | objectSrc: [ '\'none\'' ], // only define to allow plugins, else let defaultSrc 'none' block it |
14 | formAction: ["'self'"], | 14 | formAction: [ '\'self\'' ], |
15 | frameAncestors: ["'none'"], | 15 | frameAncestors: [ '\'none\'' ], |
16 | baseUri: ["'self'"], | 16 | baseUri: [ '\'self\'' ], |
17 | manifestSrc: ["'self'"], | 17 | manifestSrc: [ '\'self\'' ], |
18 | frameSrc: ["'self'"], // instead of deprecated child-src / self because of test-embed | 18 | frameSrc: [ '\'self\'' ], // instead of deprecated child-src / self because of test-embed |
19 | workerSrc: ["'self'", 'blob:'] // instead of deprecated child-src | 19 | workerSrc: [ '\'self\'', 'blob:' ] // instead of deprecated child-src |
20 | }, | 20 | }, |
21 | CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {}, | 21 | CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {}, |
22 | CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {} | 22 | CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {} |
@@ -29,7 +29,7 @@ const baseCSP = helmet.contentSecurityPolicy({ | |||
29 | }) | 29 | }) |
30 | 30 | ||
31 | const embedCSP = helmet.contentSecurityPolicy({ | 31 | const embedCSP = helmet.contentSecurityPolicy({ |
32 | directives: Object.assign({}, baseDirectives, { frameAncestors: ['*'] }), | 32 | directives: Object.assign({}, baseDirectives, { frameAncestors: [ '*' ] }), |
33 | browserSniff: false, // assumes a modern browser, but allows CDN in front | 33 | browserSniff: false, // assumes a modern browser, but allows CDN in front |
34 | reportOnly: CONFIG.CSP.REPORT_ONLY | 34 | reportOnly: CONFIG.CSP.REPORT_ONLY |
35 | }) | 35 | }) |
diff --git a/server/middlewares/dnt.ts b/server/middlewares/dnt.ts index 607def855..dd88005dd 100644 --- a/server/middlewares/dnt.ts +++ b/server/middlewares/dnt.ts | |||
@@ -1,6 +1,3 @@ | |||
1 | import * as ipaddr from 'ipaddr.js' | ||
2 | import { format } from 'util' | ||
3 | |||
4 | const advertiseDoNotTrack = (_, res, next) => { | 1 | const advertiseDoNotTrack = (_, res, next) => { |
5 | res.setHeader('Tk', 'N') | 2 | res.setHeader('Tk', 'N') |
6 | return next() | 3 | return next() |
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts index 749f5cccd..9d0eaa51f 100644 --- a/server/middlewares/oauth.ts +++ b/server/middlewares/oauth.ts | |||
@@ -1,17 +1,8 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as OAuthServer from 'express-oauth-server' | ||
3 | import { OAUTH_LIFETIME } from '../initializers/constants' | ||
4 | import { logger } from '../helpers/logger' | 2 | import { logger } from '../helpers/logger' |
5 | import { Socket } from 'socket.io' | 3 | import { Socket } from 'socket.io' |
6 | import { getAccessToken } from '../lib/oauth-model' | 4 | import { getAccessToken } from '../lib/oauth-model' |
7 | 5 | import { oAuthServer } from '@server/lib/auth' | |
8 | const oAuthServer = new OAuthServer({ | ||
9 | useErrorHandler: true, | ||
10 | accessTokenLifetime: OAUTH_LIFETIME.ACCESS_TOKEN, | ||
11 | refreshTokenLifetime: OAUTH_LIFETIME.REFRESH_TOKEN, | ||
12 | continueMiddleware: true, | ||
13 | model: require('../lib/oauth-model') | ||
14 | }) | ||
15 | 6 | ||
16 | function authenticate (req: express.Request, res: express.Response, next: express.NextFunction, authenticateInQuery = false) { | 7 | function authenticate (req: express.Request, res: express.Response, next: express.NextFunction, authenticateInQuery = false) { |
17 | const options = authenticateInQuery ? { allowBearerTokensInQueryString: true } : {} | 8 | const options = authenticateInQuery ? { allowBearerTokensInQueryString: true } : {} |
@@ -51,6 +42,7 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) { | |||
51 | 42 | ||
52 | return next() | 43 | return next() |
53 | }) | 44 | }) |
45 | .catch(err => logger.error('Cannot get access token.', { err })) | ||
54 | } | 46 | } |
55 | 47 | ||
56 | function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) { | 48 | function authenticatePromiseIfNeeded (req: express.Request, res: express.Response, authenticateInQuery = false) { |
@@ -72,27 +64,11 @@ function optionalAuthenticate (req: express.Request, res: express.Response, next | |||
72 | return next() | 64 | return next() |
73 | } | 65 | } |
74 | 66 | ||
75 | function token (req: express.Request, res: express.Response, next: express.NextFunction) { | ||
76 | return oAuthServer.token()(req, res, err => { | ||
77 | if (err) { | ||
78 | return res.status(err.status) | ||
79 | .json({ | ||
80 | error: err.message, | ||
81 | code: err.name | ||
82 | }) | ||
83 | .end() | ||
84 | } | ||
85 | |||
86 | return next() | ||
87 | }) | ||
88 | } | ||
89 | |||
90 | // --------------------------------------------------------------------------- | 67 | // --------------------------------------------------------------------------- |
91 | 68 | ||
92 | export { | 69 | export { |
93 | authenticate, | 70 | authenticate, |
94 | authenticateSocket, | 71 | authenticateSocket, |
95 | authenticatePromiseIfNeeded, | 72 | authenticatePromiseIfNeeded, |
96 | optionalAuthenticate, | 73 | optionalAuthenticate |
97 | token | ||
98 | } | 74 | } |
diff --git a/server/middlewares/sort.ts b/server/middlewares/sort.ts index 8c27e8237..fcbb2902c 100644 --- a/server/middlewares/sort.ts +++ b/server/middlewares/sort.ts | |||
@@ -1,20 +1,14 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { SortType } from '../models/utils' | 2 | import { SortType } from '../models/utils' |
3 | 3 | ||
4 | function setDefaultSort (req: express.Request, res: express.Response, next: express.NextFunction) { | 4 | const setDefaultSort = setDefaultSortFactory('-createdAt') |
5 | if (!req.query.sort) req.query.sort = '-createdAt' | ||
6 | |||
7 | return next() | ||
8 | } | ||
9 | 5 | ||
10 | function setDefaultSearchSort (req: express.Request, res: express.Response, next: express.NextFunction) { | 6 | const setDefaultVideoRedundanciesSort = setDefaultSortFactory('name') |
11 | if (!req.query.sort) req.query.sort = '-match' | ||
12 | 7 | ||
13 | return next() | 8 | const setDefaultSearchSort = setDefaultSortFactory('-match') |
14 | } | ||
15 | 9 | ||
16 | function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) { | 10 | function setBlacklistSort (req: express.Request, res: express.Response, next: express.NextFunction) { |
17 | let newSort: SortType = { sortModel: undefined, sortValue: '' } | 11 | const newSort: SortType = { sortModel: undefined, sortValue: '' } |
18 | 12 | ||
19 | if (!req.query.sort) req.query.sort = '-createdAt' | 13 | if (!req.query.sort) req.query.sort = '-createdAt' |
20 | 14 | ||
@@ -39,5 +33,16 @@ function setBlacklistSort (req: express.Request, res: express.Response, next: ex | |||
39 | export { | 33 | export { |
40 | setDefaultSort, | 34 | setDefaultSort, |
41 | setDefaultSearchSort, | 35 | setDefaultSearchSort, |
36 | setDefaultVideoRedundanciesSort, | ||
42 | setBlacklistSort | 37 | setBlacklistSort |
43 | } | 38 | } |
39 | |||
40 | // --------------------------------------------------------------------------- | ||
41 | |||
42 | function setDefaultSortFactory (sort: string) { | ||
43 | return (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
44 | if (!req.query.sort) req.query.sort = sort | ||
45 | |||
46 | return next() | ||
47 | } | ||
48 | } | ||
diff --git a/server/middlewares/validators/activitypub/activity.ts b/server/middlewares/validators/activitypub/activity.ts index 7582f65e7..7350be5d5 100644 --- a/server/middlewares/validators/activitypub/activity.ts +++ b/server/middlewares/validators/activitypub/activity.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { isRootActivityValid } from '../../../helpers/custom-validators/activitypub/activity' | 2 | import { isRootActivityValid } from '../../../helpers/custom-validators/activitypub/activity' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getServerActor } from '../../../helpers/utils' | 4 | import { getServerActor } from '@server/models/application/application' |
5 | 5 | ||
6 | async function activityPubValidator (req: express.Request, res: express.Response, next: express.NextFunction) { | 6 | async function activityPubValidator (req: express.Request, res: express.Response, next: express.NextFunction) { |
7 | logger.debug('Checking activity pub parameters') | 7 | logger.debug('Checking activity pub parameters') |
diff --git a/server/middlewares/validators/avatar.ts b/server/middlewares/validators/avatar.ts index 8623d07e8..2acb97483 100644 --- a/server/middlewares/validators/avatar.ts +++ b/server/middlewares/validators/avatar.ts | |||
@@ -8,8 +8,8 @@ import { cleanUpReqFiles } from '../../helpers/express-utils' | |||
8 | 8 | ||
9 | const updateAvatarValidator = [ | 9 | const updateAvatarValidator = [ |
10 | body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage( | 10 | body('avatarfile').custom((value, { req }) => isAvatarFile(req.files)).withMessage( |
11 | 'This file is not supported or too large. Please, make sure it is of the following type : ' | 11 | 'This file is not supported or too large. Please, make sure it is of the following type : ' + |
12 | + CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ') | 12 | CONSTRAINTS_FIELDS.ACTORS.AVATAR.EXTNAME.join(', ') |
13 | ), | 13 | ), |
14 | 14 | ||
15 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 15 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts index 47a0b1a1c..27224ff9b 100644 --- a/server/middlewares/validators/blocklist.ts +++ b/server/middlewares/validators/blocklist.ts | |||
@@ -6,9 +6,9 @@ import { AccountBlocklistModel } from '../../models/account/account-blocklist' | |||
6 | import { isHostValid } from '../../helpers/custom-validators/servers' | 6 | import { isHostValid } from '../../helpers/custom-validators/servers' |
7 | import { ServerBlocklistModel } from '../../models/server/server-blocklist' | 7 | import { ServerBlocklistModel } from '../../models/server/server-blocklist' |
8 | import { ServerModel } from '../../models/server/server' | 8 | import { ServerModel } from '../../models/server/server' |
9 | import { getServerActor } from '../../helpers/utils' | ||
10 | import { WEBSERVER } from '../../initializers/constants' | 9 | import { WEBSERVER } from '../../initializers/constants' |
11 | import { doesAccountNameWithHostExist } from '../../helpers/middlewares' | 10 | import { doesAccountNameWithHostExist } from '../../helpers/middlewares' |
11 | import { getServerActor } from '@server/models/application/application' | ||
12 | 12 | ||
13 | const blockAccountValidator = [ | 13 | const blockAccountValidator = [ |
14 | body('accountName').exists().withMessage('Should have an account name with host'), | 14 | body('accountName').exists().withMessage('Should have an account name with host'), |
@@ -84,12 +84,7 @@ const blockServerValidator = [ | |||
84 | .end() | 84 | .end() |
85 | } | 85 | } |
86 | 86 | ||
87 | const server = await ServerModel.loadByHost(host) | 87 | const server = await ServerModel.loadOrCreateByHost(host) |
88 | if (!server) { | ||
89 | return res.status(404) | ||
90 | .send({ error: 'Server host not found.' }) | ||
91 | .end() | ||
92 | } | ||
93 | 88 | ||
94 | res.locals.server = server | 89 | res.locals.server = server |
95 | 90 | ||
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index 2d1f61947..dfa549e76 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -3,10 +3,10 @@ import { body } from 'express-validator' | |||
3 | import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' | 3 | import { isUserNSFWPolicyValid, isUserVideoQuotaDailyValid, isUserVideoQuotaValid } from '../../helpers/custom-validators/users' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
6 | import { Emailer } from '../../lib/emailer' | ||
7 | import { areValidationErrors } from './utils' | 6 | import { areValidationErrors } from './utils' |
8 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 7 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
9 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | 8 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' |
9 | import { isEmailEnabled } from '@server/initializers/config' | ||
10 | 10 | ||
11 | const customConfigUpdateValidator = [ | 11 | const customConfigUpdateValidator = [ |
12 | body('instance.name').exists().withMessage('Should have a valid instance name'), | 12 | body('instance.name').exists().withMessage('Should have a valid instance name'), |
@@ -55,7 +55,7 @@ const customConfigUpdateValidator = [ | |||
55 | 55 | ||
56 | body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), | 56 | body('theme.default').custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), |
57 | 57 | ||
58 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 58 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
59 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) | 59 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) |
60 | 60 | ||
61 | if (areValidationErrors(req, res)) return | 61 | if (areValidationErrors(req, res)) return |
@@ -73,7 +73,7 @@ export { | |||
73 | } | 73 | } |
74 | 74 | ||
75 | function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) { | 75 | function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) { |
76 | if (Emailer.isEnabled()) return true | 76 | if (isEmailEnabled()) return true |
77 | 77 | ||
78 | if (customConfig.signup.requiresEmailVerification === true) { | 78 | if (customConfig.signup.requiresEmailVerification === true) { |
79 | res.status(400) | 79 | res.status(400) |
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts index 29f6c87be..f34c2b174 100644 --- a/server/middlewares/validators/feeds.ts +++ b/server/middlewares/validators/feeds.ts | |||
@@ -22,13 +22,13 @@ function setFeedFormatContentType (req: express.Request, res: express.Response, | |||
22 | 22 | ||
23 | let acceptableContentTypes: string[] | 23 | let acceptableContentTypes: string[] |
24 | if (format === 'atom' || format === 'atom1') { | 24 | if (format === 'atom' || format === 'atom1') { |
25 | acceptableContentTypes = ['application/atom+xml', 'application/xml', 'text/xml'] | 25 | acceptableContentTypes = [ 'application/atom+xml', 'application/xml', 'text/xml' ] |
26 | } else if (format === 'json' || format === 'json1') { | 26 | } else if (format === 'json' || format === 'json1') { |
27 | acceptableContentTypes = ['application/json'] | 27 | acceptableContentTypes = [ 'application/json' ] |
28 | } else if (format === 'rss' || format === 'rss2') { | 28 | } else if (format === 'rss' || format === 'rss2') { |
29 | acceptableContentTypes = ['application/rss+xml', 'application/xml', 'text/xml'] | 29 | acceptableContentTypes = [ 'application/rss+xml', 'application/xml', 'text/xml' ] |
30 | } else { | 30 | } else { |
31 | acceptableContentTypes = ['application/xml', 'text/xml'] | 31 | acceptableContentTypes = [ 'application/xml', 'text/xml' ] |
32 | } | 32 | } |
33 | 33 | ||
34 | if (req.accepts(acceptableContentTypes)) { | 34 | if (req.accepts(acceptableContentTypes)) { |
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index a98d32d86..7808135f7 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts | |||
@@ -3,7 +3,6 @@ import { body, param, query } from 'express-validator' | |||
3 | import { isTestInstance } from '../../helpers/core-utils' | 3 | import { isTestInstance } from '../../helpers/core-utils' |
4 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' | 4 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { getServerActor } from '../../helpers/utils' | ||
7 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' | 6 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' |
8 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 7 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
9 | import { areValidationErrors } from './utils' | 8 | import { areValidationErrors } from './utils' |
@@ -12,6 +11,7 @@ import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | |||
12 | import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | 11 | import { isActorTypeValid, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' |
13 | import { MActorFollowActorsDefault } from '@server/typings/models' | 12 | import { MActorFollowActorsDefault } from '@server/typings/models' |
14 | import { isFollowStateValid } from '@server/helpers/custom-validators/follows' | 13 | import { isFollowStateValid } from '@server/helpers/custom-validators/follows' |
14 | import { getServerActor } from '@server/models/application/application' | ||
15 | 15 | ||
16 | const listFollowsValidator = [ | 16 | const listFollowsValidator = [ |
17 | query('state') | 17 | query('state') |
diff --git a/server/middlewares/validators/plugins.ts b/server/middlewares/validators/plugins.ts index 910d03c29..2cb49ec43 100644 --- a/server/middlewares/validators/plugins.ts +++ b/server/middlewares/validators/plugins.ts | |||
@@ -1,33 +1,72 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param, query } from 'express-validator' | 2 | import { body, param, query, ValidationChain } from 'express-validator' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { areValidationErrors } from './utils' | 4 | import { areValidationErrors } from './utils' |
5 | import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' | 5 | import { isNpmPluginNameValid, isPluginNameValid, isPluginTypeValid, isPluginVersionValid } from '../../helpers/custom-validators/plugins' |
6 | import { PluginManager } from '../../lib/plugins/plugin-manager' | 6 | import { PluginManager } from '../../lib/plugins/plugin-manager' |
7 | import { isBooleanValid, isSafePath, toBooleanOrNull } from '../../helpers/custom-validators/misc' | 7 | import { isBooleanValid, isSafePath, toBooleanOrNull, exists } from '../../helpers/custom-validators/misc' |
8 | import { PluginModel } from '../../models/server/plugin' | 8 | import { PluginModel } from '../../models/server/plugin' |
9 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' | 9 | import { InstallOrUpdatePlugin } from '../../../shared/models/plugins/install-plugin.model' |
10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' |
11 | import { CONFIG } from '../../initializers/config' | 11 | import { CONFIG } from '../../initializers/config' |
12 | 12 | ||
13 | const servePluginStaticDirectoryValidator = (pluginType: PluginType) => [ | 13 | const getPluginValidator = (pluginType: PluginType, withVersion = true) => { |
14 | param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name'), | 14 | const validators: (ValidationChain | express.Handler)[] = [ |
15 | param('pluginVersion').custom(isPluginVersionValid).withMessage('Should have a valid plugin version'), | 15 | param('pluginName').custom(isPluginNameValid).withMessage('Should have a valid plugin name') |
16 | param('staticEndpoint').custom(isSafePath).withMessage('Should have a valid static endpoint'), | 16 | ] |
17 | |||
18 | if (withVersion) { | ||
19 | validators.push( | ||
20 | param('pluginVersion').custom(isPluginVersionValid).withMessage('Should have a valid plugin version') | ||
21 | ) | ||
22 | } | ||
23 | |||
24 | return validators.concat([ | ||
25 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
26 | logger.debug('Checking getPluginValidator parameters', { parameters: req.params }) | ||
27 | |||
28 | if (areValidationErrors(req, res)) return | ||
29 | |||
30 | const npmName = PluginModel.buildNpmName(req.params.pluginName, pluginType) | ||
31 | const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(npmName) | ||
32 | |||
33 | if (!plugin) return res.sendStatus(404) | ||
34 | if (withVersion && plugin.version !== req.params.pluginVersion) return res.sendStatus(404) | ||
35 | |||
36 | res.locals.registeredPlugin = plugin | ||
37 | |||
38 | return next() | ||
39 | } | ||
40 | ]) | ||
41 | } | ||
42 | |||
43 | const getExternalAuthValidator = [ | ||
44 | param('authName').custom(exists).withMessage('Should have a valid auth name'), | ||
17 | 45 | ||
18 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 46 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
19 | logger.debug('Checking servePluginStaticDirectory parameters', { parameters: req.params }) | 47 | logger.debug('Checking getExternalAuthValidator parameters', { parameters: req.params }) |
20 | 48 | ||
21 | if (areValidationErrors(req, res)) return | 49 | if (areValidationErrors(req, res)) return |
22 | 50 | ||
23 | const npmName = PluginModel.buildNpmName(req.params.pluginName, pluginType) | 51 | const plugin = res.locals.registeredPlugin |
24 | const plugin = PluginManager.Instance.getRegisteredPluginOrTheme(npmName) | 52 | if (!plugin.registerHelpersStore) return res.sendStatus(404) |
25 | 53 | ||
26 | if (!plugin || plugin.version !== req.params.pluginVersion) { | 54 | const externalAuth = plugin.registerHelpersStore.getExternalAuths().find(a => a.authName === req.params.authName) |
27 | return res.sendStatus(404) | 55 | if (!externalAuth) return res.sendStatus(404) |
28 | } | 56 | |
57 | res.locals.externalAuth = externalAuth | ||
58 | |||
59 | return next() | ||
60 | } | ||
61 | ] | ||
29 | 62 | ||
30 | res.locals.registeredPlugin = plugin | 63 | const pluginStaticDirectoryValidator = [ |
64 | param('staticEndpoint').custom(isSafePath).withMessage('Should have a valid static endpoint'), | ||
65 | |||
66 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
67 | logger.debug('Checking pluginStaticDirectoryValidator parameters', { parameters: req.params }) | ||
68 | |||
69 | if (areValidationErrors(req, res)) return | ||
31 | 70 | ||
32 | return next() | 71 | return next() |
33 | } | 72 | } |
@@ -149,11 +188,13 @@ const listAvailablePluginsValidator = [ | |||
149 | // --------------------------------------------------------------------------- | 188 | // --------------------------------------------------------------------------- |
150 | 189 | ||
151 | export { | 190 | export { |
152 | servePluginStaticDirectoryValidator, | 191 | pluginStaticDirectoryValidator, |
192 | getPluginValidator, | ||
153 | updatePluginSettingsValidator, | 193 | updatePluginSettingsValidator, |
154 | uninstallPluginValidator, | 194 | uninstallPluginValidator, |
155 | listAvailablePluginsValidator, | 195 | listAvailablePluginsValidator, |
156 | existingPluginValidator, | 196 | existingPluginValidator, |
157 | installOrUpdatePluginValidator, | 197 | installOrUpdatePluginValidator, |
158 | listPluginsValidator | 198 | listPluginsValidator, |
199 | getExternalAuthValidator | ||
159 | } | 200 | } |
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts index 8098e3a44..8cd3bc33d 100644 --- a/server/middlewares/validators/redundancy.ts +++ b/server/middlewares/validators/redundancy.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { exists, isBooleanValid, isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 3 | import { exists, isBooleanValid, isIdOrUUIDValid, isIdValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { areValidationErrors } from './utils' | 5 | import { areValidationErrors } from './utils' |
6 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 6 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
7 | import { isHostValid } from '../../helpers/custom-validators/servers' | 7 | import { isHostValid } from '../../helpers/custom-validators/servers' |
8 | import { ServerModel } from '../../models/server/server' | 8 | import { ServerModel } from '../../models/server/server' |
9 | import { doesVideoExist } from '../../helpers/middlewares' | 9 | import { doesVideoExist } from '../../helpers/middlewares' |
10 | import { isVideoRedundancyTarget } from '@server/helpers/custom-validators/video-redundancies' | ||
10 | 11 | ||
11 | const videoFileRedundancyGetValidator = [ | 12 | const videoFileRedundancyGetValidator = [ |
12 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 13 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), |
@@ -101,10 +102,77 @@ const updateServerRedundancyValidator = [ | |||
101 | } | 102 | } |
102 | ] | 103 | ] |
103 | 104 | ||
105 | const listVideoRedundanciesValidator = [ | ||
106 | query('target') | ||
107 | .custom(isVideoRedundancyTarget).withMessage('Should have a valid video redundancies target'), | ||
108 | |||
109 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
110 | logger.debug('Checking listVideoRedundanciesValidator parameters', { parameters: req.query }) | ||
111 | |||
112 | if (areValidationErrors(req, res)) return | ||
113 | |||
114 | return next() | ||
115 | } | ||
116 | ] | ||
117 | |||
118 | const addVideoRedundancyValidator = [ | ||
119 | body('videoId') | ||
120 | .custom(isIdValid) | ||
121 | .withMessage('Should have a valid video id'), | ||
122 | |||
123 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
124 | logger.debug('Checking addVideoRedundancyValidator parameters', { parameters: req.query }) | ||
125 | |||
126 | if (areValidationErrors(req, res)) return | ||
127 | |||
128 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return | ||
129 | |||
130 | if (res.locals.onlyVideo.remote === false) { | ||
131 | return res.status(400) | ||
132 | .json({ error: 'Cannot create a redundancy on a local video' }) | ||
133 | .end() | ||
134 | } | ||
135 | |||
136 | const alreadyExists = await VideoRedundancyModel.isLocalByVideoUUIDExists(res.locals.onlyVideo.uuid) | ||
137 | if (alreadyExists) { | ||
138 | return res.status(409) | ||
139 | .json({ error: 'This video is already duplicated by your instance.' }) | ||
140 | } | ||
141 | |||
142 | return next() | ||
143 | } | ||
144 | ] | ||
145 | |||
146 | const removeVideoRedundancyValidator = [ | ||
147 | param('redundancyId') | ||
148 | .custom(isIdValid) | ||
149 | .withMessage('Should have a valid redundancy id'), | ||
150 | |||
151 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
152 | logger.debug('Checking removeVideoRedundancyValidator parameters', { parameters: req.query }) | ||
153 | |||
154 | if (areValidationErrors(req, res)) return | ||
155 | |||
156 | const redundancy = await VideoRedundancyModel.loadByIdWithVideo(parseInt(req.params.redundancyId, 10)) | ||
157 | if (!redundancy) { | ||
158 | return res.status(404) | ||
159 | .json({ error: 'Video redundancy not found' }) | ||
160 | .end() | ||
161 | } | ||
162 | |||
163 | res.locals.videoRedundancy = redundancy | ||
164 | |||
165 | return next() | ||
166 | } | ||
167 | ] | ||
168 | |||
104 | // --------------------------------------------------------------------------- | 169 | // --------------------------------------------------------------------------- |
105 | 170 | ||
106 | export { | 171 | export { |
107 | videoFileRedundancyGetValidator, | 172 | videoFileRedundancyGetValidator, |
108 | videoPlaylistRedundancyGetValidator, | 173 | videoPlaylistRedundancyGetValidator, |
109 | updateServerRedundancyValidator | 174 | updateServerRedundancyValidator, |
175 | listVideoRedundanciesValidator, | ||
176 | addVideoRedundancyValidator, | ||
177 | removeVideoRedundancyValidator | ||
110 | } | 178 | } |
diff --git a/server/middlewares/validators/server.ts b/server/middlewares/validators/server.ts index f6812647b..6158c3363 100644 --- a/server/middlewares/validators/server.ts +++ b/server/middlewares/validators/server.ts | |||
@@ -5,9 +5,8 @@ import { isHostValid, isValidContactBody } from '../../helpers/custom-validators | |||
5 | import { ServerModel } from '../../models/server/server' | 5 | import { ServerModel } from '../../models/server/server' |
6 | import { body } from 'express-validator' | 6 | import { body } from 'express-validator' |
7 | import { isUserDisplayNameValid } from '../../helpers/custom-validators/users' | 7 | import { isUserDisplayNameValid } from '../../helpers/custom-validators/users' |
8 | import { Emailer } from '../../lib/emailer' | ||
9 | import { Redis } from '../../lib/redis' | 8 | import { Redis } from '../../lib/redis' |
10 | import { CONFIG } from '../../initializers/config' | 9 | import { CONFIG, isEmailEnabled } from '../../initializers/config' |
11 | 10 | ||
12 | const serverGetValidator = [ | 11 | const serverGetValidator = [ |
13 | body('host').custom(isHostValid).withMessage('Should have a valid host'), | 12 | body('host').custom(isHostValid).withMessage('Should have a valid host'), |
@@ -50,7 +49,7 @@ const contactAdministratorValidator = [ | |||
50 | .end() | 49 | .end() |
51 | } | 50 | } |
52 | 51 | ||
53 | if (Emailer.isEnabled() === false) { | 52 | if (isEmailEnabled() === false) { |
54 | return res | 53 | return res |
55 | .status(409) | 54 | .status(409) |
56 | .send({ error: 'Emailer is not enabled on this instance.' }) | 55 | .send({ error: 'Emailer is not enabled on this instance.' }) |
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index c75e701d6..b76dab722 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -23,6 +23,7 @@ const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUM | |||
23 | const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) | 23 | const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) |
24 | const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS) | 24 | const SORTABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.PLUGINS) |
25 | const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) | 25 | const SORTABLE_AVAILABLE_PLUGINS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.AVAILABLE_PLUGINS) |
26 | const SORTABLE_VIDEO_REDUNDANCIES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_REDUNDANCIES) | ||
26 | 27 | ||
27 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) | 28 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) |
28 | const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) | 29 | const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) |
@@ -45,6 +46,7 @@ const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COL | |||
45 | const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS) | 46 | const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS) |
46 | const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS) | 47 | const pluginsSortValidator = checkSort(SORTABLE_PLUGINS_COLUMNS) |
47 | const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS) | 48 | const availablePluginsSortValidator = checkSort(SORTABLE_AVAILABLE_PLUGINS_COLUMNS) |
49 | const videoRedundanciesSortValidator = checkSort(SORTABLE_VIDEO_REDUNDANCIES_COLUMNS) | ||
48 | 50 | ||
49 | // --------------------------------------------------------------------------- | 51 | // --------------------------------------------------------------------------- |
50 | 52 | ||
@@ -69,5 +71,6 @@ export { | |||
69 | serversBlocklistSortValidator, | 71 | serversBlocklistSortValidator, |
70 | userNotificationsSortValidator, | 72 | userNotificationsSortValidator, |
71 | videoPlaylistsSortValidator, | 73 | videoPlaylistsSortValidator, |
74 | videoRedundanciesSortValidator, | ||
72 | pluginsSortValidator | 75 | pluginsSortValidator |
73 | } | 76 | } |
diff --git a/server/middlewares/validators/themes.ts b/server/middlewares/validators/themes.ts index 24a9673f7..82794656d 100644 --- a/server/middlewares/validators/themes.ts +++ b/server/middlewares/validators/themes.ts | |||
@@ -16,7 +16,7 @@ const serveThemeCSSValidator = [ | |||
16 | 16 | ||
17 | if (areValidationErrors(req, res)) return | 17 | if (areValidationErrors(req, res)) return |
18 | 18 | ||
19 | const theme = PluginManager.Instance.getRegisteredTheme(req.params.themeName) | 19 | const theme = PluginManager.Instance.getRegisteredThemeByShortName(req.params.themeName) |
20 | 20 | ||
21 | if (!theme || theme.version !== req.params.themeVersion) { | 21 | if (!theme || theme.version !== req.params.themeVersion) { |
22 | return res.sendStatus(404) | 22 | return res.sendStatus(404) |
diff --git a/server/middlewares/validators/user-subscriptions.ts b/server/middlewares/validators/user-subscriptions.ts index 9bc8c87e7..5d4cc94c5 100644 --- a/server/middlewares/validators/user-subscriptions.ts +++ b/server/middlewares/validators/user-subscriptions.ts | |||
@@ -53,7 +53,6 @@ const userSubscriptionGetValidator = [ | |||
53 | .json({ | 53 | .json({ |
54 | error: `Subscription ${req.params.uri} not found.` | 54 | error: `Subscription ${req.params.uri} not found.` |
55 | }) | 55 | }) |
56 | .end() | ||
57 | } | 56 | } |
58 | 57 | ||
59 | res.locals.subscription = subscription | 58 | res.locals.subscription = subscription |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index c78c67a8c..840b9fc74 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { body, param } from 'express-validator' | 3 | import { body, param, query } from 'express-validator' |
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
5 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 5 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
6 | import { | 6 | import { |
@@ -14,6 +14,7 @@ import { | |||
14 | isUserDisplayNameValid, | 14 | isUserDisplayNameValid, |
15 | isUserNSFWPolicyValid, | 15 | isUserNSFWPolicyValid, |
16 | isUserPasswordValid, | 16 | isUserPasswordValid, |
17 | isUserPasswordValidOrEmpty, | ||
17 | isUserRoleValid, | 18 | isUserRoleValid, |
18 | isUserUsernameValid, | 19 | isUserUsernameValid, |
19 | isUserVideoLanguages, | 20 | isUserVideoLanguages, |
@@ -36,11 +37,10 @@ import { doesVideoExist } from '../../helpers/middlewares' | |||
36 | import { UserRole } from '../../../shared/models/users' | 37 | import { UserRole } from '../../../shared/models/users' |
37 | import { MUserDefault } from '@server/typings/models' | 38 | import { MUserDefault } from '@server/typings/models' |
38 | import { Hooks } from '@server/lib/plugins/hooks' | 39 | import { Hooks } from '@server/lib/plugins/hooks' |
39 | import { isLocalVideoAccepted } from '@server/lib/moderation' | ||
40 | 40 | ||
41 | const usersAddValidator = [ | 41 | const usersAddValidator = [ |
42 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), | 42 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), |
43 | body('password').custom(isUserPasswordValid).withMessage('Should have a valid password'), | 43 | body('password').custom(isUserPasswordValidOrEmpty).withMessage('Should have a valid password'), |
44 | body('email').isEmail().withMessage('Should have a valid email'), | 44 | body('email').isEmail().withMessage('Should have a valid email'), |
45 | body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), | 45 | body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), |
46 | body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'), | 46 | body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'), |
@@ -149,7 +149,7 @@ const usersBlockingValidator = [ | |||
149 | ] | 149 | ] |
150 | 150 | ||
151 | const deleteMeValidator = [ | 151 | const deleteMeValidator = [ |
152 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 152 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
153 | const user = res.locals.oauth.token.User | 153 | const user = res.locals.oauth.token.User |
154 | if (user.username === 'root') { | 154 | if (user.username === 'root') { |
155 | return res.status(400) | 155 | return res.status(400) |
@@ -256,12 +256,13 @@ const usersUpdateMeValidator = [ | |||
256 | 256 | ||
257 | const usersGetValidator = [ | 257 | const usersGetValidator = [ |
258 | param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), | 258 | param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), |
259 | query('withStats').optional().isBoolean().withMessage('Should have a valid stats flag'), | ||
259 | 260 | ||
260 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 261 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
261 | logger.debug('Checking usersGet parameters', { parameters: req.params }) | 262 | logger.debug('Checking usersGet parameters', { parameters: req.params }) |
262 | 263 | ||
263 | if (areValidationErrors(req, res)) return | 264 | if (areValidationErrors(req, res)) return |
264 | if (!await checkUserIdExist(req.params.id, res)) return | 265 | if (!await checkUserIdExist(req.params.id, res, req.query.withStats)) return |
265 | 266 | ||
266 | return next() | 267 | return next() |
267 | } | 268 | } |
@@ -303,7 +304,7 @@ const ensureUserRegistrationAllowed = [ | |||
303 | ] | 304 | ] |
304 | 305 | ||
305 | const ensureUserRegistrationAllowedForIP = [ | 306 | const ensureUserRegistrationAllowedForIP = [ |
306 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 307 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
307 | const allowed = isSignupAllowedForCurrentIP(req.ip) | 308 | const allowed = isSignupAllowedForCurrentIP(req.ip) |
308 | 309 | ||
309 | if (allowed === false) { | 310 | if (allowed === false) { |
@@ -410,7 +411,7 @@ const userAutocompleteValidator = [ | |||
410 | ] | 411 | ] |
411 | 412 | ||
412 | const ensureAuthUserOwnsAccountValidator = [ | 413 | const ensureAuthUserOwnsAccountValidator = [ |
413 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 414 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
414 | const user = res.locals.oauth.token.User | 415 | const user = res.locals.oauth.token.User |
415 | 416 | ||
416 | if (res.locals.account.id !== user.Account.id) { | 417 | if (res.locals.account.id !== user.Account.id) { |
@@ -460,9 +461,9 @@ export { | |||
460 | 461 | ||
461 | // --------------------------------------------------------------------------- | 462 | // --------------------------------------------------------------------------- |
462 | 463 | ||
463 | function checkUserIdExist (idArg: number | string, res: express.Response) { | 464 | function checkUserIdExist (idArg: number | string, res: express.Response, withStats = false) { |
464 | const id = parseInt(idArg + '', 10) | 465 | const id = parseInt(idArg + '', 10) |
465 | return checkUserExist(() => UserModel.loadById(id), res) | 466 | return checkUserExist(() => UserModel.loadById(id, withStats), res) |
466 | } | 467 | } |
467 | 468 | ||
468 | function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) { | 469 | function checkUserEmailExist (email: string, res: express.Response, abortResponse = true) { |
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts index a4aef4024..901997bcb 100644 --- a/server/middlewares/validators/videos/video-abuses.ts +++ b/server/middlewares/validators/videos/video-abuses.ts | |||
@@ -1,14 +1,15 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 3 | import { exists, isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' |
4 | import { logger } from '../../../helpers/logger' | ||
5 | import { areValidationErrors } from '../utils' | ||
6 | import { | 4 | import { |
5 | isAbuseVideoIsValid, | ||
7 | isVideoAbuseModerationCommentValid, | 6 | isVideoAbuseModerationCommentValid, |
8 | isVideoAbuseReasonValid, | 7 | isVideoAbuseReasonValid, |
9 | isVideoAbuseStateValid | 8 | isVideoAbuseStateValid |
10 | } from '../../../helpers/custom-validators/video-abuses' | 9 | } from '../../../helpers/custom-validators/video-abuses' |
10 | import { logger } from '../../../helpers/logger' | ||
11 | import { doesVideoAbuseExist, doesVideoExist } from '../../../helpers/middlewares' | 11 | import { doesVideoAbuseExist, doesVideoExist } from '../../../helpers/middlewares' |
12 | import { areValidationErrors } from '../utils' | ||
12 | 13 | ||
13 | const videoAbuseReportValidator = [ | 14 | const videoAbuseReportValidator = [ |
14 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 15 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), |
@@ -32,8 +33,7 @@ const videoAbuseGetValidator = [ | |||
32 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) | 33 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) |
33 | 34 | ||
34 | if (areValidationErrors(req, res)) return | 35 | if (areValidationErrors(req, res)) return |
35 | if (!await doesVideoExist(req.params.videoId, res)) return | 36 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return |
36 | if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return | ||
37 | 37 | ||
38 | return next() | 38 | return next() |
39 | } | 39 | } |
@@ -53,8 +53,42 @@ const videoAbuseUpdateValidator = [ | |||
53 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) | 53 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) |
54 | 54 | ||
55 | if (areValidationErrors(req, res)) return | 55 | if (areValidationErrors(req, res)) return |
56 | if (!await doesVideoExist(req.params.videoId, res)) return | 56 | if (!await doesVideoAbuseExist(req.params.id, req.params.videoId, res)) return |
57 | if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return | 57 | |
58 | return next() | ||
59 | } | ||
60 | ] | ||
61 | |||
62 | const videoAbuseListValidator = [ | ||
63 | query('id') | ||
64 | .optional() | ||
65 | .custom(isIdValid).withMessage('Should have a valid id'), | ||
66 | query('search') | ||
67 | .optional() | ||
68 | .custom(exists).withMessage('Should have a valid search'), | ||
69 | query('state') | ||
70 | .optional() | ||
71 | .custom(isVideoAbuseStateValid).withMessage('Should have a valid video abuse state'), | ||
72 | query('videoIs') | ||
73 | .optional() | ||
74 | .custom(isAbuseVideoIsValid).withMessage('Should have a valid "video is" attribute'), | ||
75 | query('searchReporter') | ||
76 | .optional() | ||
77 | .custom(exists).withMessage('Should have a valid reporter search'), | ||
78 | query('searchReportee') | ||
79 | .optional() | ||
80 | .custom(exists).withMessage('Should have a valid reportee search'), | ||
81 | query('searchVideo') | ||
82 | .optional() | ||
83 | .custom(exists).withMessage('Should have a valid video search'), | ||
84 | query('searchVideoChannel') | ||
85 | .optional() | ||
86 | .custom(exists).withMessage('Should have a valid video channel search'), | ||
87 | |||
88 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
89 | logger.debug('Checking videoAbuseListValidator parameters', { parameters: req.body }) | ||
90 | |||
91 | if (areValidationErrors(req, res)) return | ||
58 | 92 | ||
59 | return next() | 93 | return next() |
60 | } | 94 | } |
@@ -63,6 +97,7 @@ const videoAbuseUpdateValidator = [ | |||
63 | // --------------------------------------------------------------------------- | 97 | // --------------------------------------------------------------------------- |
64 | 98 | ||
65 | export { | 99 | export { |
100 | videoAbuseListValidator, | ||
66 | videoAbuseReportValidator, | 101 | videoAbuseReportValidator, |
67 | videoAbuseGetValidator, | 102 | videoAbuseGetValidator, |
68 | videoAbuseUpdateValidator | 103 | videoAbuseUpdateValidator |
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts index 5440e57e7..4bd6a8333 100644 --- a/server/middlewares/validators/videos/video-blacklist.ts +++ b/server/middlewares/validators/videos/video-blacklist.ts | |||
@@ -69,6 +69,10 @@ const videosBlacklistFiltersValidator = [ | |||
69 | query('type') | 69 | query('type') |
70 | .optional() | 70 | .optional() |
71 | .custom(isVideoBlacklistTypeValid).withMessage('Should have a valid video blacklist type attribute'), | 71 | .custom(isVideoBlacklistTypeValid).withMessage('Should have a valid video blacklist type attribute'), |
72 | query('search') | ||
73 | .optional() | ||
74 | .not() | ||
75 | .isEmpty().withMessage('Should have a valid search'), | ||
72 | 76 | ||
73 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 77 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
74 | logger.debug('Checking videos blacklist filters query', { parameters: req.query }) | 78 | logger.debug('Checking videos blacklist filters query', { parameters: req.query }) |
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts index 7b0cd6f66..872d9c2ab 100644 --- a/server/middlewares/validators/videos/video-captions.ts +++ b/server/middlewares/validators/videos/video-captions.ts | |||
@@ -13,10 +13,12 @@ const addVideoCaptionValidator = [ | |||
13 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 13 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), |
14 | param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), | 14 | param('captionLanguage').custom(isVideoCaptionLanguageValid).not().isEmpty().withMessage('Should have a valid caption language'), |
15 | body('captionfile') | 15 | body('captionfile') |
16 | .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile')).withMessage( | 16 | .custom((_, { req }) => isVideoCaptionFile(req.files, 'captionfile')) |
17 | `This caption file is not supported or too large. Please, make sure it is under ${CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE} and one of the following mimetypes: ` | 17 | .withMessage( |
18 | + Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT).map(key => `${key} (${MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT[key]})`).join(', ') | 18 | 'This caption file is not supported or too large. ' + |
19 | ), | 19 | `Please, make sure it is under ${CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE} and one of the following mimetypes: ` + |
20 | Object.keys(MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT).map(key => `${key} (${MIMETYPES.VIDEO_CAPTIONS.MIMETYPE_EXT[key]})`).join(', ') | ||
21 | ), | ||
20 | 22 | ||
21 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 23 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
22 | logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) | 24 | logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) |
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index ebce14714..882fb2b84 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body, param, query } from 'express-validator' |
3 | import { UserRight } from '../../../../shared' | 3 | import { UserRight } from '../../../../shared' |
4 | import { | 4 | import { |
5 | isVideoChannelDescriptionValid, | 5 | isVideoChannelDescriptionValid, |
@@ -128,6 +128,15 @@ const localVideoChannelValidator = [ | |||
128 | } | 128 | } |
129 | ] | 129 | ] |
130 | 130 | ||
131 | const videoChannelStatsValidator = [ | ||
132 | query('withStats').optional().isBoolean().withMessage('Should have a valid stats flag'), | ||
133 | |||
134 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
135 | if (areValidationErrors(req, res)) return | ||
136 | return next() | ||
137 | } | ||
138 | ] | ||
139 | |||
131 | // --------------------------------------------------------------------------- | 140 | // --------------------------------------------------------------------------- |
132 | 141 | ||
133 | export { | 142 | export { |
@@ -135,7 +144,8 @@ export { | |||
135 | videoChannelsUpdateValidator, | 144 | videoChannelsUpdateValidator, |
136 | videoChannelsRemoveValidator, | 145 | videoChannelsRemoveValidator, |
137 | videoChannelsNameWithHostValidator, | 146 | videoChannelsNameWithHostValidator, |
138 | localVideoChannelValidator | 147 | localVideoChannelValidator, |
148 | videoChannelStatsValidator | ||
139 | } | 149 | } |
140 | 150 | ||
141 | // --------------------------------------------------------------------------- | 151 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts index 77c5f940d..4846a5e9e 100644 --- a/server/middlewares/validators/videos/video-comments.ts +++ b/server/middlewares/validators/videos/video-comments.ts | |||
@@ -1,16 +1,16 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator' | 2 | import { body, param } from 'express-validator' |
3 | import { MUserAccountUrl } from '@server/typings/models' | ||
3 | import { UserRight } from '../../../../shared' | 4 | import { UserRight } from '../../../../shared' |
4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 5 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' |
5 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' | 6 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' |
6 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { doesVideoExist } from '../../../helpers/middlewares' | ||
9 | import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation' | ||
10 | import { Hooks } from '../../../lib/plugins/hooks' | ||
7 | import { VideoCommentModel } from '../../../models/video/video-comment' | 11 | import { VideoCommentModel } from '../../../models/video/video-comment' |
12 | import { MCommentOwnerVideoReply, MVideo, MVideoFullLight, MVideoId } from '../../../typings/models/video' | ||
8 | import { areValidationErrors } from '../utils' | 13 | import { areValidationErrors } from '../utils' |
9 | import { Hooks } from '../../../lib/plugins/hooks' | ||
10 | import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation' | ||
11 | import { doesVideoExist } from '../../../helpers/middlewares' | ||
12 | import { MCommentOwner, MVideo, MVideoFullLight, MVideoId } from '../../../typings/models/video' | ||
13 | import { MUser } from '@server/typings/models' | ||
14 | 14 | ||
15 | const listVideoCommentThreadsValidator = [ | 15 | const listVideoCommentThreadsValidator = [ |
16 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 16 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), |
@@ -50,7 +50,7 @@ const addVideoCommentThreadValidator = [ | |||
50 | if (areValidationErrors(req, res)) return | 50 | if (areValidationErrors(req, res)) return |
51 | if (!await doesVideoExist(req.params.videoId, res)) return | 51 | if (!await doesVideoExist(req.params.videoId, res)) return |
52 | if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return | 52 | if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return |
53 | if (!await isVideoCommentAccepted(req, res, res.locals.videoAll,false)) return | 53 | if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, false)) return |
54 | 54 | ||
55 | return next() | 55 | return next() |
56 | } | 56 | } |
@@ -188,7 +188,7 @@ function isVideoCommentsEnabled (video: MVideo, res: express.Response) { | |||
188 | return true | 188 | return true |
189 | } | 189 | } |
190 | 190 | ||
191 | function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwner, res: express.Response) { | 191 | function checkUserCanDeleteVideoComment (user: MUserAccountUrl, videoComment: MCommentOwnerVideoReply, res: express.Response) { |
192 | if (videoComment.isDeleted()) { | 192 | if (videoComment.isDeleted()) { |
193 | res.status(409) | 193 | res.status(409) |
194 | .json({ error: 'This comment is already deleted' }) | 194 | .json({ error: 'This comment is already deleted' }) |
@@ -196,11 +196,16 @@ function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwne | |||
196 | return false | 196 | return false |
197 | } | 197 | } |
198 | 198 | ||
199 | const account = videoComment.Account | 199 | const userAccount = user.Account |
200 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { | 200 | |
201 | if ( | ||
202 | user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && // Not a moderator | ||
203 | videoComment.accountId !== userAccount.id && // Not the comment owner | ||
204 | videoComment.Video.VideoChannel.accountId !== userAccount.id // Not the video owner | ||
205 | ) { | ||
201 | res.status(403) | 206 | res.status(403) |
202 | .json({ error: 'Cannot remove video comment of another user' }) | 207 | .json({ error: 'Cannot remove video comment of another user' }) |
203 | .end() | 208 | |
204 | return false | 209 | return false |
205 | } | 210 | } |
206 | 211 | ||
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts index 318dad100..5dc5db533 100644 --- a/server/middlewares/validators/videos/video-imports.ts +++ b/server/middlewares/validators/videos/video-imports.ts | |||
@@ -22,10 +22,11 @@ const videoImportAddValidator = getCommonVideoEditAttributes().concat([ | |||
22 | .optional() | 22 | .optional() |
23 | .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'), | 23 | .custom(isVideoMagnetUriValid).withMessage('Should have a valid video magnet URI'), |
24 | body('torrentfile') | 24 | body('torrentfile') |
25 | .custom((value, { req }) => isVideoImportTorrentFile(req.files)).withMessage( | 25 | .custom((value, { req }) => isVideoImportTorrentFile(req.files)) |
26 | 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' | 26 | .withMessage( |
27 | + CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ') | 27 | 'This torrent file is not supported or too large. Please, make sure it is of the following type: ' + |
28 | ), | 28 | CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.EXTNAME.join(', ') |
29 | ), | ||
29 | body('name') | 30 | body('name') |
30 | .optional() | 31 | .optional() |
31 | .custom(isVideoNameValid).withMessage('Should have a valid name'), | 32 | .custom(isVideoNameValid).withMessage('Should have a valid name'), |
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 1d67e8666..6b15c5464 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -384,10 +384,11 @@ export { | |||
384 | function getCommonPlaylistEditAttributes () { | 384 | function getCommonPlaylistEditAttributes () { |
385 | return [ | 385 | return [ |
386 | body('thumbnailfile') | 386 | body('thumbnailfile') |
387 | .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( | 387 | .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')) |
388 | 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' | 388 | .withMessage( |
389 | + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ') | 389 | 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' + |
390 | ), | 390 | CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ') |
391 | ), | ||
391 | 392 | ||
392 | body('description') | 393 | body('description') |
393 | .optional() | 394 | .optional() |
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts index 4021cfecc..cbc144f69 100644 --- a/server/middlewares/validators/videos/video-rates.ts +++ b/server/middlewares/validators/videos/video-rates.ts | |||
@@ -24,7 +24,7 @@ const videoUpdateRateValidator = [ | |||
24 | } | 24 | } |
25 | ] | 25 | ] |
26 | 26 | ||
27 | const getAccountVideoRateValidator = function (rateType: VideoRateType) { | 27 | const getAccountVideoRateValidatorFactory = function (rateType: VideoRateType) { |
28 | return [ | 28 | return [ |
29 | param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), | 29 | param('name').custom(isAccountNameValid).withMessage('Should have a valid account name'), |
30 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 30 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), |
@@ -51,7 +51,7 @@ const getAccountVideoRateValidator = function (rateType: VideoRateType) { | |||
51 | const videoRatingValidator = [ | 51 | const videoRatingValidator = [ |
52 | query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'), | 52 | query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'), |
53 | 53 | ||
54 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 54 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
55 | logger.debug('Checking rating parameter', { parameters: req.params }) | 55 | logger.debug('Checking rating parameter', { parameters: req.params }) |
56 | 56 | ||
57 | if (areValidationErrors(req, res)) return | 57 | if (areValidationErrors(req, res)) return |
@@ -64,6 +64,6 @@ const videoRatingValidator = [ | |||
64 | 64 | ||
65 | export { | 65 | export { |
66 | videoUpdateRateValidator, | 66 | videoUpdateRateValidator, |
67 | getAccountVideoRateValidator, | 67 | getAccountVideoRateValidatorFactory, |
68 | videoRatingValidator | 68 | videoRatingValidator |
69 | } | 69 | } |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 6733d9dec..867c05fc1 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -29,7 +29,7 @@ import { | |||
29 | } from '../../../helpers/custom-validators/videos' | 29 | } from '../../../helpers/custom-validators/videos' |
30 | import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils' | 30 | import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils' |
31 | import { logger } from '../../../helpers/logger' | 31 | import { logger } from '../../../helpers/logger' |
32 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 32 | import { CONSTRAINTS_FIELDS, OVERVIEWS } from '../../../initializers/constants' |
33 | import { authenticatePromiseIfNeeded } from '../../oauth' | 33 | import { authenticatePromiseIfNeeded } from '../../oauth' |
34 | import { areValidationErrors } from '../utils' | 34 | import { areValidationErrors } from '../utils' |
35 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 35 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
@@ -38,19 +38,24 @@ import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } f | |||
38 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' | 38 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' |
39 | import { AccountModel } from '../../../models/account/account' | 39 | import { AccountModel } from '../../../models/account/account' |
40 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' | 40 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' |
41 | import { getServerActor } from '../../../helpers/utils' | ||
42 | import { CONFIG } from '../../../initializers/config' | 41 | import { CONFIG } from '../../../initializers/config' |
43 | import { isLocalVideoAccepted } from '../../../lib/moderation' | 42 | import { isLocalVideoAccepted } from '../../../lib/moderation' |
44 | import { Hooks } from '../../../lib/plugins/hooks' | 43 | import { Hooks } from '../../../lib/plugins/hooks' |
45 | import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../../../helpers/middlewares' | 44 | import { |
45 | checkUserCanManageVideo, | ||
46 | doesVideoChannelOfAccountExist, | ||
47 | doesVideoExist, | ||
48 | doesVideoFileOfVideoExist | ||
49 | } from '../../../helpers/middlewares' | ||
46 | import { MVideoFullLight } from '@server/typings/models' | 50 | import { MVideoFullLight } from '@server/typings/models' |
47 | import { getVideoWithAttributes } from '../../../helpers/video' | 51 | import { getVideoWithAttributes } from '../../../helpers/video' |
52 | import { getServerActor } from '@server/models/application/application' | ||
48 | 53 | ||
49 | const videosAddValidator = getCommonVideoEditAttributes().concat([ | 54 | const videosAddValidator = getCommonVideoEditAttributes().concat([ |
50 | body('videofile') | 55 | body('videofile') |
51 | .custom((value, { req }) => isVideoFile(req.files)).withMessage( | 56 | .custom((value, { req }) => isVideoFile(req.files)).withMessage( |
52 | 'This file is not supported or too large. Please, make sure it is of the following type: ' | 57 | 'This file is not supported or too large. Please, make sure it is of the following type: ' + |
53 | + CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') | 58 | CONSTRAINTS_FIELDS.VIDEOS.EXTNAME.join(', ') |
54 | ), | 59 | ), |
55 | body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), | 60 | body('name').custom(isVideoNameValid).withMessage('Should have a valid name'), |
56 | body('channelId') | 61 | body('channelId') |
@@ -147,7 +152,10 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R | |||
147 | }) | 152 | }) |
148 | } | 153 | } |
149 | 154 | ||
150 | const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights', authenticateInQuery = false) => { | 155 | const videosCustomGetValidator = ( |
156 | fetchType: 'all' | 'only-video' | 'only-video-with-rights' | 'only-immutable-attributes', | ||
157 | authenticateInQuery = false | ||
158 | ) => { | ||
151 | return [ | 159 | return [ |
152 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 160 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
153 | 161 | ||
@@ -157,6 +165,9 @@ const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video- | |||
157 | if (areValidationErrors(req, res)) return | 165 | if (areValidationErrors(req, res)) return |
158 | if (!await doesVideoExist(req.params.id, res, fetchType)) return | 166 | if (!await doesVideoExist(req.params.id, res, fetchType)) return |
159 | 167 | ||
168 | // Controllers does not need to check video rights | ||
169 | if (fetchType === 'only-immutable-attributes') return next() | ||
170 | |||
160 | const video = getVideoWithAttributes(res) | 171 | const video = getVideoWithAttributes(res) |
161 | const videoAll = video as MVideoFullLight | 172 | const videoAll = video as MVideoFullLight |
162 | 173 | ||
@@ -192,6 +203,20 @@ const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video- | |||
192 | const videosGetValidator = videosCustomGetValidator('all') | 203 | const videosGetValidator = videosCustomGetValidator('all') |
193 | const videosDownloadValidator = videosCustomGetValidator('all', true) | 204 | const videosDownloadValidator = videosCustomGetValidator('all', true) |
194 | 205 | ||
206 | const videoFileMetadataGetValidator = getCommonVideoEditAttributes().concat([ | ||
207 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | ||
208 | param('videoFileId').custom(isIdValid).not().isEmpty().withMessage('Should have a valid videoFileId'), | ||
209 | |||
210 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
211 | logger.debug('Checking videoFileMetadataGet parameters', { parameters: req.params }) | ||
212 | |||
213 | if (areValidationErrors(req, res)) return | ||
214 | if (!await doesVideoFileOfVideoExist(+req.params.videoFileId, req.params.id, res)) return | ||
215 | |||
216 | return next() | ||
217 | } | ||
218 | ]) | ||
219 | |||
195 | const videosRemoveValidator = [ | 220 | const videosRemoveValidator = [ |
196 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 221 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
197 | 222 | ||
@@ -245,19 +270,15 @@ const videosTerminateChangeOwnershipValidator = [ | |||
245 | // Check if the user who did the request is able to change the ownership of the video | 270 | // Check if the user who did the request is able to change the ownership of the video |
246 | if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return | 271 | if (!checkUserCanTerminateOwnershipChange(res.locals.oauth.token.User, res.locals.videoChangeOwnership, res)) return |
247 | 272 | ||
248 | return next() | ||
249 | }, | ||
250 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
251 | const videoChangeOwnership = res.locals.videoChangeOwnership | 273 | const videoChangeOwnership = res.locals.videoChangeOwnership |
252 | 274 | ||
253 | if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { | 275 | if (videoChangeOwnership.status !== VideoChangeOwnershipStatus.WAITING) { |
254 | return next() | ||
255 | } else { | ||
256 | res.status(403) | 276 | res.status(403) |
257 | .json({ error: 'Ownership already accepted or refused' }) | 277 | .json({ error: 'Ownership already accepted or refused' }) |
258 | |||
259 | return | 278 | return |
260 | } | 279 | } |
280 | |||
281 | return next() | ||
261 | } | 282 | } |
262 | ] | 283 | ] |
263 | 284 | ||
@@ -280,18 +301,31 @@ const videosAcceptChangeOwnershipValidator = [ | |||
280 | } | 301 | } |
281 | ] | 302 | ] |
282 | 303 | ||
304 | const videosOverviewValidator = [ | ||
305 | query('page') | ||
306 | .optional() | ||
307 | .isInt({ min: 1, max: OVERVIEWS.VIDEOS.SAMPLES_COUNT }) | ||
308 | .withMessage('Should have a valid pagination'), | ||
309 | |||
310 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
311 | if (areValidationErrors(req, res)) return | ||
312 | |||
313 | return next() | ||
314 | } | ||
315 | ] | ||
316 | |||
283 | function getCommonVideoEditAttributes () { | 317 | function getCommonVideoEditAttributes () { |
284 | return [ | 318 | return [ |
285 | body('thumbnailfile') | 319 | body('thumbnailfile') |
286 | .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( | 320 | .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( |
287 | 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' | 321 | 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' + |
288 | + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') | 322 | CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') |
289 | ), | 323 | ), |
290 | body('previewfile') | 324 | body('previewfile') |
291 | .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( | 325 | .custom((value, { req }) => isVideoImage(req.files, 'previewfile')).withMessage( |
292 | 'This preview file is not supported or too large. Please, make sure it is of the following type: ' | 326 | 'This preview file is not supported or too large. Please, make sure it is of the following type: ' + |
293 | + CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') | 327 | CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME.join(', ') |
294 | ), | 328 | ), |
295 | 329 | ||
296 | body('category') | 330 | body('category') |
297 | .optional() | 331 | .optional() |
@@ -409,6 +443,7 @@ export { | |||
409 | videosAddValidator, | 443 | videosAddValidator, |
410 | videosUpdateValidator, | 444 | videosUpdateValidator, |
411 | videosGetValidator, | 445 | videosGetValidator, |
446 | videoFileMetadataGetValidator, | ||
412 | videosDownloadValidator, | 447 | videosDownloadValidator, |
413 | checkVideoFollowConstraints, | 448 | checkVideoFollowConstraints, |
414 | videosCustomGetValidator, | 449 | videosCustomGetValidator, |
@@ -420,7 +455,9 @@ export { | |||
420 | 455 | ||
421 | getCommonVideoEditAttributes, | 456 | getCommonVideoEditAttributes, |
422 | 457 | ||
423 | commonVideosFiltersValidator | 458 | commonVideosFiltersValidator, |
459 | |||
460 | videosOverviewValidator | ||
424 | } | 461 | } |
425 | 462 | ||
426 | // --------------------------------------------------------------------------- | 463 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts index d50e6527f..5fe864f8b 100644 --- a/server/middlewares/validators/webfinger.ts +++ b/server/middlewares/validators/webfinger.ts | |||
@@ -18,15 +18,14 @@ const webfingerValidator = [ | |||
18 | const nameWithHost = getHostWithPort(req.query.resource.substr(5)) | 18 | const nameWithHost = getHostWithPort(req.query.resource.substr(5)) |
19 | const [ name ] = nameWithHost.split('@') | 19 | const [ name ] = nameWithHost.split('@') |
20 | 20 | ||
21 | // FIXME: we don't need the full actor | 21 | const actor = await ActorModel.loadLocalUrlByName(name) |
22 | const actor = await ActorModel.loadLocalByName(name) | ||
23 | if (!actor) { | 22 | if (!actor) { |
24 | return res.status(404) | 23 | return res.status(404) |
25 | .send({ error: 'Actor not found' }) | 24 | .send({ error: 'Actor not found' }) |
26 | .end() | 25 | .end() |
27 | } | 26 | } |
28 | 27 | ||
29 | res.locals.actorFull = actor | 28 | res.locals.actorUrl = actor |
30 | return next() | 29 | return next() |
31 | } | 30 | } |
32 | ] | 31 | ] |
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index 6ebe32556..d8a7ce4b4 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { AccountModel } from './account' | 2 | import { AccountModel } from './account' |
3 | import { getSort } from '../utils' | 3 | import { getSort, searchAttribute } from '../utils' |
4 | import { AccountBlock } from '../../../shared/models/blocklist' | 4 | import { AccountBlock } from '../../../shared/models/blocklist' |
5 | import { Op } from 'sequelize' | 5 | import { Op } from 'sequelize' |
6 | import * as Bluebird from 'bluebird' | 6 | import * as Bluebird from 'bluebird' |
@@ -80,7 +80,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
80 | attributes: [ 'accountId', 'id' ], | 80 | attributes: [ 'accountId', 'id' ], |
81 | where: { | 81 | where: { |
82 | accountId: { | 82 | accountId: { |
83 | [Op.in]: accountIds // FIXME: sequelize ANY seems broken | 83 | [Op.in]: accountIds |
84 | }, | 84 | }, |
85 | targetAccountId | 85 | targetAccountId |
86 | }, | 86 | }, |
@@ -111,16 +111,36 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
111 | return AccountBlocklistModel.findOne(query) | 111 | return AccountBlocklistModel.findOne(query) |
112 | } | 112 | } |
113 | 113 | ||
114 | static listForApi (accountId: number, start: number, count: number, sort: string) { | 114 | static listForApi (parameters: { |
115 | start: number | ||
116 | count: number | ||
117 | sort: string | ||
118 | search?: string | ||
119 | accountId: number | ||
120 | }) { | ||
121 | const { start, count, sort, search, accountId } = parameters | ||
122 | |||
115 | const query = { | 123 | const query = { |
116 | offset: start, | 124 | offset: start, |
117 | limit: count, | 125 | limit: count, |
118 | order: getSort(sort), | 126 | order: getSort(sort) |
119 | where: { | 127 | } |
120 | accountId | 128 | |
121 | } | 129 | const where = { |
130 | accountId | ||
122 | } | 131 | } |
123 | 132 | ||
133 | if (search) { | ||
134 | Object.assign(where, { | ||
135 | [Op.or]: [ | ||
136 | searchAttribute(search, '$BlockedAccount.name$'), | ||
137 | searchAttribute(search, '$BlockedAccount.Actor.url$') | ||
138 | ] | ||
139 | }) | ||
140 | } | ||
141 | |||
142 | Object.assign(query, { where }) | ||
143 | |||
124 | return AccountBlocklistModel | 144 | return AccountBlocklistModel |
125 | .scope([ ScopeNames.WITH_ACCOUNTS ]) | 145 | .scope([ ScopeNames.WITH_ACCOUNTS ]) |
126 | .findAndCountAll<MAccountBlocklistAccounts>(query) | 146 | .findAndCountAll<MAccountBlocklistAccounts>(query) |
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index c593595b2..8aeb486d1 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -99,7 +99,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
99 | static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> { | 99 | static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> { |
100 | const options: FindOptions = { | 100 | const options: FindOptions = { |
101 | where: { | 101 | where: { |
102 | [ Op.or]: [ | 102 | [Op.or]: [ |
103 | { | 103 | { |
104 | accountId, | 104 | accountId, |
105 | videoId | 105 | videoId |
@@ -116,10 +116,10 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
116 | } | 116 | } |
117 | 117 | ||
118 | static listByAccountForApi (options: { | 118 | static listByAccountForApi (options: { |
119 | start: number, | 119 | start: number |
120 | count: number, | 120 | count: number |
121 | sort: string, | 121 | sort: string |
122 | type?: string, | 122 | type?: string |
123 | accountId: number | 123 | accountId: number |
124 | }) { | 124 | }) { |
125 | const query: FindOptions = { | 125 | const query: FindOptions = { |
@@ -135,7 +135,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
135 | required: true, | 135 | required: true, |
136 | include: [ | 136 | include: [ |
137 | { | 137 | { |
138 | model: VideoChannelModel.scope({ method: [VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), | 138 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), |
139 | required: true | 139 | required: true |
140 | } | 140 | } |
141 | ] | 141 | ] |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 8a0ffeb63..a0081f259 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -32,8 +32,9 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ | |||
32 | import { AccountBlocklistModel } from './account-blocklist' | 32 | import { AccountBlocklistModel } from './account-blocklist' |
33 | import { ServerBlocklistModel } from '../server/server-blocklist' | 33 | import { ServerBlocklistModel } from '../server/server-blocklist' |
34 | import { ActorFollowModel } from '../activitypub/actor-follow' | 34 | import { ActorFollowModel } from '../activitypub/actor-follow' |
35 | import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models' | 35 | import { MAccountActor, MAccountAP, MAccountDefault, MAccountFormattable, MAccountSummaryFormattable } from '../../typings/models' |
36 | import * as Bluebird from 'bluebird' | 36 | import * as Bluebird from 'bluebird' |
37 | import { ModelCache } from '@server/models/model-cache' | ||
37 | 38 | ||
38 | export enum ScopeNames { | 39 | export enum ScopeNames { |
39 | SUMMARY = 'SUMMARY' | 40 | SUMMARY = 'SUMMARY' |
@@ -53,7 +54,7 @@ export type SummaryOptions = { | |||
53 | ] | 54 | ] |
54 | })) | 55 | })) |
55 | @Scopes(() => ({ | 56 | @Scopes(() => ({ |
56 | [ ScopeNames.SUMMARY ]: (options: SummaryOptions = {}) => { | 57 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { |
57 | const whereActor = options.whereActor || undefined | 58 | const whereActor = options.whereActor || undefined |
58 | 59 | ||
59 | const serverInclude: IncludeOptions = { | 60 | const serverInclude: IncludeOptions = { |
@@ -218,8 +219,6 @@ export class AccountModel extends Model<AccountModel> { | |||
218 | }) | 219 | }) |
219 | BlockedAccounts: AccountBlocklistModel[] | 220 | BlockedAccounts: AccountBlocklistModel[] |
220 | 221 | ||
221 | private static cache: { [ id: string ]: any } = {} | ||
222 | |||
223 | @BeforeDestroy | 222 | @BeforeDestroy |
224 | static async sendDeleteIfOwned (instance: AccountModel, options) { | 223 | static async sendDeleteIfOwned (instance: AccountModel, options) { |
225 | if (!instance.Actor) { | 224 | if (!instance.Actor) { |
@@ -247,45 +246,43 @@ export class AccountModel extends Model<AccountModel> { | |||
247 | } | 246 | } |
248 | 247 | ||
249 | static loadLocalByName (name: string): Bluebird<MAccountDefault> { | 248 | static loadLocalByName (name: string): Bluebird<MAccountDefault> { |
250 | // The server actor never change, so we can easily cache it | 249 | const fun = () => { |
251 | if (name === SERVER_ACTOR_NAME && AccountModel.cache[name]) { | 250 | const query = { |
252 | return Bluebird.resolve(AccountModel.cache[name]) | 251 | where: { |
253 | } | 252 | [Op.or]: [ |
254 | 253 | { | |
255 | const query = { | 254 | userId: { |
256 | where: { | 255 | [Op.ne]: null |
257 | [ Op.or ]: [ | 256 | } |
258 | { | 257 | }, |
259 | userId: { | 258 | { |
260 | [ Op.ne ]: null | 259 | applicationId: { |
260 | [Op.ne]: null | ||
261 | } | ||
261 | } | 262 | } |
262 | }, | 263 | ] |
264 | }, | ||
265 | include: [ | ||
263 | { | 266 | { |
264 | applicationId: { | 267 | model: ActorModel, |
265 | [ Op.ne ]: null | 268 | required: true, |
269 | where: { | ||
270 | preferredUsername: name | ||
266 | } | 271 | } |
267 | } | 272 | } |
268 | ] | 273 | ] |
269 | }, | 274 | } |
270 | include: [ | ||
271 | { | ||
272 | model: ActorModel, | ||
273 | required: true, | ||
274 | where: { | ||
275 | preferredUsername: name | ||
276 | } | ||
277 | } | ||
278 | ] | ||
279 | } | ||
280 | 275 | ||
281 | return AccountModel.findOne(query) | 276 | return AccountModel.findOne(query) |
282 | .then(account => { | 277 | } |
283 | if (name === SERVER_ACTOR_NAME) { | ||
284 | AccountModel.cache[name] = account | ||
285 | } | ||
286 | 278 | ||
287 | return account | 279 | return ModelCache.Instance.doCache({ |
288 | }) | 280 | cacheType: 'local-account-name', |
281 | key: name, | ||
282 | fun, | ||
283 | // The server actor never change, so we can easily cache it | ||
284 | whitelist: () => name === SERVER_ACTOR_NAME | ||
285 | }) | ||
289 | } | 286 | } |
290 | 287 | ||
291 | static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> { | 288 | static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> { |
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index a05f30175..5a725187a 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts | |||
@@ -363,7 +363,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
363 | where: { | 363 | where: { |
364 | userId, | 364 | userId, |
365 | id: { | 365 | id: { |
366 | [Op.in]: notificationIds // FIXME: sequelize ANY seems broken | 366 | [Op.in]: notificationIds |
367 | } | 367 | } |
368 | } | 368 | } |
369 | } | 369 | } |
@@ -379,7 +379,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
379 | 379 | ||
380 | toFormattedJSON (this: UserNotificationModelForApi): UserNotification { | 380 | toFormattedJSON (this: UserNotificationModelForApi): UserNotification { |
381 | const video = this.Video | 381 | const video = this.Video |
382 | ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) | 382 | ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) }) |
383 | : undefined | 383 | : undefined |
384 | 384 | ||
385 | const videoImport = this.VideoImport ? { | 385 | const videoImport = this.VideoImport ? { |
diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts index 3fe4c8db1..522eebeaf 100644 --- a/server/models/account/user-video-history.ts +++ b/server/models/account/user-video-history.ts | |||
@@ -59,7 +59,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> { | |||
59 | return VideoModel.listForApi({ | 59 | return VideoModel.listForApi({ |
60 | start, | 60 | start, |
61 | count, | 61 | count, |
62 | sort: '-UserVideoHistories.updatedAt', | 62 | sort: '-"userVideoHistory"."updatedAt"', |
63 | nsfw: null, // All | 63 | nsfw: null, // All |
64 | includeLocalVideos: true, | 64 | includeLocalVideos: true, |
65 | withFiles: false, | 65 | withFiles: false, |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 4c2c5e278..fbd3080c6 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { FindOptions, literal, Op, QueryTypes, where, fn, col } from 'sequelize' | 1 | import { col, FindOptions, fn, literal, Op, QueryTypes, where, WhereOptions } from 'sequelize' |
2 | import { | 2 | import { |
3 | AfterDestroy, | 3 | AfterDestroy, |
4 | AfterUpdate, | 4 | AfterUpdate, |
@@ -19,7 +19,7 @@ import { | |||
19 | Table, | 19 | Table, |
20 | UpdatedAt | 20 | UpdatedAt |
21 | } from 'sequelize-typescript' | 21 | } from 'sequelize-typescript' |
22 | import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, VideoPlaylistType, VideoPrivacy } from '../../../shared' | 22 | import { hasUserRight, MyUser, USER_ROLE_LABELS, UserRight, VideoAbuseState, VideoPlaylistType, VideoPrivacy } from '../../../shared' |
23 | import { User, UserRole } from '../../../shared/models/users' | 23 | import { User, UserRole } from '../../../shared/models/users' |
24 | import { | 24 | import { |
25 | isNoInstanceConfigWarningModal, | 25 | isNoInstanceConfigWarningModal, |
@@ -49,7 +49,7 @@ import { VideoPlaylistModel } from '../video/video-playlist' | |||
49 | import { AccountModel } from './account' | 49 | import { AccountModel } from './account' |
50 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' | 50 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' |
51 | import { values } from 'lodash' | 51 | import { values } from 'lodash' |
52 | import { DEFAULT_THEME_NAME, DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' | 52 | import { DEFAULT_USER_THEME_NAME, NSFW_POLICY_TYPES } from '../../initializers/constants' |
53 | import { clearCacheByUserId } from '../../lib/oauth-model' | 53 | import { clearCacheByUserId } from '../../lib/oauth-model' |
54 | import { UserNotificationSettingModel } from './user-notification-setting' | 54 | import { UserNotificationSettingModel } from './user-notification-setting' |
55 | import { VideoModel } from '../video/video' | 55 | import { VideoModel } from '../video/video' |
@@ -71,7 +71,9 @@ import { | |||
71 | } from '@server/typings/models' | 71 | } from '@server/typings/models' |
72 | 72 | ||
73 | enum ScopeNames { | 73 | enum ScopeNames { |
74 | FOR_ME_API = 'FOR_ME_API' | 74 | FOR_ME_API = 'FOR_ME_API', |
75 | WITH_VIDEOCHANNELS = 'WITH_VIDEOCHANNELS', | ||
76 | WITH_STATS = 'WITH_STATS' | ||
75 | } | 77 | } |
76 | 78 | ||
77 | @DefaultScope(() => ({ | 79 | @DefaultScope(() => ({ |
@@ -101,7 +103,7 @@ enum ScopeNames { | |||
101 | required: true, | 103 | required: true, |
102 | where: { | 104 | where: { |
103 | type: { | 105 | type: { |
104 | [ Op.ne ]: VideoPlaylistType.REGULAR | 106 | [Op.ne]: VideoPlaylistType.REGULAR |
105 | } | 107 | } |
106 | } | 108 | } |
107 | } | 109 | } |
@@ -112,6 +114,96 @@ enum ScopeNames { | |||
112 | required: true | 114 | required: true |
113 | } | 115 | } |
114 | ] | 116 | ] |
117 | }, | ||
118 | [ScopeNames.WITH_VIDEOCHANNELS]: { | ||
119 | include: [ | ||
120 | { | ||
121 | model: AccountModel, | ||
122 | include: [ | ||
123 | { | ||
124 | model: VideoChannelModel | ||
125 | }, | ||
126 | { | ||
127 | attributes: [ 'id', 'name', 'type' ], | ||
128 | model: VideoPlaylistModel.unscoped(), | ||
129 | required: true, | ||
130 | where: { | ||
131 | type: { | ||
132 | [Op.ne]: VideoPlaylistType.REGULAR | ||
133 | } | ||
134 | } | ||
135 | } | ||
136 | ] | ||
137 | } | ||
138 | ] | ||
139 | }, | ||
140 | [ScopeNames.WITH_STATS]: { | ||
141 | attributes: { | ||
142 | include: [ | ||
143 | [ | ||
144 | literal( | ||
145 | '(' + | ||
146 | UserModel.generateUserQuotaBaseSQL({ | ||
147 | withSelect: false, | ||
148 | whereUserId: '"UserModel"."id"' | ||
149 | }) + | ||
150 | ')' | ||
151 | ), | ||
152 | 'videoQuotaUsed' | ||
153 | ], | ||
154 | [ | ||
155 | literal( | ||
156 | '(' + | ||
157 | 'SELECT COUNT("video"."id") ' + | ||
158 | 'FROM "video" ' + | ||
159 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
160 | 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + | ||
161 | 'WHERE "account"."userId" = "UserModel"."id"' + | ||
162 | ')' | ||
163 | ), | ||
164 | 'videosCount' | ||
165 | ], | ||
166 | [ | ||
167 | literal( | ||
168 | '(' + | ||
169 | `SELECT concat_ws(':', "abuses", "acceptedAbuses") ` + | ||
170 | 'FROM (' + | ||
171 | 'SELECT COUNT("videoAbuse"."id") AS "abuses", ' + | ||
172 | `COUNT("videoAbuse"."id") FILTER (WHERE "videoAbuse"."state" = ${VideoAbuseState.ACCEPTED}) AS "acceptedAbuses" ` + | ||
173 | 'FROM "videoAbuse" ' + | ||
174 | 'INNER JOIN "video" ON "videoAbuse"."videoId" = "video"."id" ' + | ||
175 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
176 | 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + | ||
177 | 'WHERE "account"."userId" = "UserModel"."id"' + | ||
178 | ') t' + | ||
179 | ')' | ||
180 | ), | ||
181 | 'videoAbusesCount' | ||
182 | ], | ||
183 | [ | ||
184 | literal( | ||
185 | '(' + | ||
186 | 'SELECT COUNT("videoAbuse"."id") ' + | ||
187 | 'FROM "videoAbuse" ' + | ||
188 | 'INNER JOIN "account" ON "account"."id" = "videoAbuse"."reporterAccountId" ' + | ||
189 | 'WHERE "account"."userId" = "UserModel"."id"' + | ||
190 | ')' | ||
191 | ), | ||
192 | 'videoAbusesCreatedCount' | ||
193 | ], | ||
194 | [ | ||
195 | literal( | ||
196 | '(' + | ||
197 | 'SELECT COUNT("videoComment"."id") ' + | ||
198 | 'FROM "videoComment" ' + | ||
199 | 'INNER JOIN "account" ON "account"."id" = "videoComment"."accountId" ' + | ||
200 | 'WHERE "account"."userId" = "UserModel"."id"' + | ||
201 | ')' | ||
202 | ), | ||
203 | 'videoCommentsCount' | ||
204 | ] | ||
205 | ] | ||
206 | } | ||
115 | } | 207 | } |
116 | })) | 208 | })) |
117 | @Table({ | 209 | @Table({ |
@@ -129,13 +221,13 @@ enum ScopeNames { | |||
129 | }) | 221 | }) |
130 | export class UserModel extends Model<UserModel> { | 222 | export class UserModel extends Model<UserModel> { |
131 | 223 | ||
132 | @AllowNull(false) | 224 | @AllowNull(true) |
133 | @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password')) | 225 | @Is('UserPassword', value => throwIfNotValid(value, isUserPasswordValid, 'user password', true)) |
134 | @Column | 226 | @Column |
135 | password: string | 227 | password: string |
136 | 228 | ||
137 | @AllowNull(false) | 229 | @AllowNull(false) |
138 | @Is('UserPassword', value => throwIfNotValid(value, isUserUsernameValid, 'user name')) | 230 | @Is('UserUsername', value => throwIfNotValid(value, isUserUsernameValid, 'user name')) |
139 | @Column | 231 | @Column |
140 | username: string | 232 | username: string |
141 | 233 | ||
@@ -186,7 +278,10 @@ export class UserModel extends Model<UserModel> { | |||
186 | 278 | ||
187 | @AllowNull(false) | 279 | @AllowNull(false) |
188 | @Default(true) | 280 | @Default(true) |
189 | @Is('UserAutoPlayNextVideoPlaylist', value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean')) | 281 | @Is( |
282 | 'UserAutoPlayNextVideoPlaylist', | ||
283 | value => throwIfNotValid(value, isUserAutoPlayNextVideoPlaylistValid, 'auto play next video for playlists boolean') | ||
284 | ) | ||
190 | @Column | 285 | @Column |
191 | autoPlayNextVideoPlaylist: boolean | 286 | autoPlayNextVideoPlaylist: boolean |
192 | 287 | ||
@@ -230,7 +325,7 @@ export class UserModel extends Model<UserModel> { | |||
230 | videoQuotaDaily: number | 325 | videoQuotaDaily: number |
231 | 326 | ||
232 | @AllowNull(false) | 327 | @AllowNull(false) |
233 | @Default(DEFAULT_THEME_NAME) | 328 | @Default(DEFAULT_USER_THEME_NAME) |
234 | @Is('UserTheme', value => throwIfNotValid(value, isThemeNameValid, 'theme')) | 329 | @Is('UserTheme', value => throwIfNotValid(value, isThemeNameValid, 'theme')) |
235 | @Column | 330 | @Column |
236 | theme: string | 331 | theme: string |
@@ -253,6 +348,16 @@ export class UserModel extends Model<UserModel> { | |||
253 | @Column | 348 | @Column |
254 | noWelcomeModal: boolean | 349 | noWelcomeModal: boolean |
255 | 350 | ||
351 | @AllowNull(true) | ||
352 | @Default(null) | ||
353 | @Column | ||
354 | pluginAuth: string | ||
355 | |||
356 | @AllowNull(true) | ||
357 | @Default(null) | ||
358 | @Column | ||
359 | lastLoginDate: Date | ||
360 | |||
256 | @CreatedAt | 361 | @CreatedAt |
257 | createdAt: Date | 362 | createdAt: Date |
258 | 363 | ||
@@ -288,7 +393,7 @@ export class UserModel extends Model<UserModel> { | |||
288 | @BeforeCreate | 393 | @BeforeCreate |
289 | @BeforeUpdate | 394 | @BeforeUpdate |
290 | static cryptPasswordIfNeeded (instance: UserModel) { | 395 | static cryptPasswordIfNeeded (instance: UserModel) { |
291 | if (instance.changed('password')) { | 396 | if (instance.changed('password') && instance.password) { |
292 | return cryptPassword(instance.password) | 397 | return cryptPassword(instance.password) |
293 | .then(hash => { | 398 | .then(hash => { |
294 | instance.password = hash | 399 | instance.password = hash |
@@ -308,7 +413,8 @@ export class UserModel extends Model<UserModel> { | |||
308 | } | 413 | } |
309 | 414 | ||
310 | static listForApi (start: number, count: number, sort: string, search?: string) { | 415 | static listForApi (start: number, count: number, sort: string, search?: string) { |
311 | let where = undefined | 416 | let where: WhereOptions |
417 | |||
312 | if (search) { | 418 | if (search) { |
313 | where = { | 419 | where = { |
314 | [Op.or]: [ | 420 | [Op.or]: [ |
@@ -319,7 +425,7 @@ export class UserModel extends Model<UserModel> { | |||
319 | }, | 425 | }, |
320 | { | 426 | { |
321 | username: { | 427 | username: { |
322 | [ Op.iLike ]: '%' + search + '%' | 428 | [Op.iLike]: '%' + search + '%' |
323 | } | 429 | } |
324 | } | 430 | } |
325 | ] | 431 | ] |
@@ -332,18 +438,14 @@ export class UserModel extends Model<UserModel> { | |||
332 | [ | 438 | [ |
333 | literal( | 439 | literal( |
334 | '(' + | 440 | '(' + |
335 | 'SELECT COALESCE(SUM("size"), 0) ' + | 441 | UserModel.generateUserQuotaBaseSQL({ |
336 | 'FROM (' + | 442 | withSelect: false, |
337 | 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' + | 443 | whereUserId: '"UserModel"."id"' |
338 | 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + | 444 | }) + |
339 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
340 | 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + | ||
341 | 'WHERE "account"."userId" = "UserModel"."id" GROUP BY "video"."id"' + | ||
342 | ') t' + | ||
343 | ')' | 445 | ')' |
344 | ), | 446 | ), |
345 | 'videoQuotaUsed' | 447 | 'videoQuotaUsed' |
346 | ] | 448 | ] as any // FIXME: typings |
347 | ] | 449 | ] |
348 | }, | 450 | }, |
349 | offset: start, | 451 | offset: start, |
@@ -353,18 +455,18 @@ export class UserModel extends Model<UserModel> { | |||
353 | } | 455 | } |
354 | 456 | ||
355 | return UserModel.findAndCountAll(query) | 457 | return UserModel.findAndCountAll(query) |
356 | .then(({ rows, count }) => { | 458 | .then(({ rows, count }) => { |
357 | return { | 459 | return { |
358 | data: rows, | 460 | data: rows, |
359 | total: count | 461 | total: count |
360 | } | 462 | } |
361 | }) | 463 | }) |
362 | } | 464 | } |
363 | 465 | ||
364 | static listWithRight (right: UserRight): Bluebird<MUserDefault[]> { | 466 | static listWithRight (right: UserRight): Bluebird<MUserDefault[]> { |
365 | const roles = Object.keys(USER_ROLE_LABELS) | 467 | const roles = Object.keys(USER_ROLE_LABELS) |
366 | .map(k => parseInt(k, 10) as UserRole) | 468 | .map(k => parseInt(k, 10) as UserRole) |
367 | .filter(role => hasUserRight(role, right)) | 469 | .filter(role => hasUserRight(role, right)) |
368 | 470 | ||
369 | const query = { | 471 | const query = { |
370 | where: { | 472 | where: { |
@@ -390,7 +492,7 @@ export class UserModel extends Model<UserModel> { | |||
390 | required: true, | 492 | required: true, |
391 | include: [ | 493 | include: [ |
392 | { | 494 | { |
393 | attributes: [ ], | 495 | attributes: [], |
394 | model: ActorModel.unscoped(), | 496 | model: ActorModel.unscoped(), |
395 | required: true, | 497 | required: true, |
396 | where: { | 498 | where: { |
@@ -398,7 +500,7 @@ export class UserModel extends Model<UserModel> { | |||
398 | }, | 500 | }, |
399 | include: [ | 501 | include: [ |
400 | { | 502 | { |
401 | attributes: [ ], | 503 | attributes: [], |
402 | as: 'ActorFollowings', | 504 | as: 'ActorFollowings', |
403 | model: ActorFollowModel.unscoped(), | 505 | model: ActorFollowModel.unscoped(), |
404 | required: true, | 506 | required: true, |
@@ -426,14 +528,20 @@ export class UserModel extends Model<UserModel> { | |||
426 | return UserModel.findAll(query) | 528 | return UserModel.findAll(query) |
427 | } | 529 | } |
428 | 530 | ||
429 | static loadById (id: number): Bluebird<MUserDefault> { | 531 | static loadById (id: number, withStats = false): Bluebird<MUserDefault> { |
430 | return UserModel.findByPk(id) | 532 | const scopes = [ |
533 | ScopeNames.WITH_VIDEOCHANNELS | ||
534 | ] | ||
535 | |||
536 | if (withStats) scopes.push(ScopeNames.WITH_STATS) | ||
537 | |||
538 | return UserModel.scope(scopes).findByPk(id) | ||
431 | } | 539 | } |
432 | 540 | ||
433 | static loadByUsername (username: string): Bluebird<MUserDefault> { | 541 | static loadByUsername (username: string): Bluebird<MUserDefault> { |
434 | const query = { | 542 | const query = { |
435 | where: { | 543 | where: { |
436 | username: { [ Op.iLike ]: username } | 544 | username: { [Op.iLike]: username } |
437 | } | 545 | } |
438 | } | 546 | } |
439 | 547 | ||
@@ -443,7 +551,7 @@ export class UserModel extends Model<UserModel> { | |||
443 | static loadForMeAPI (username: string): Bluebird<MUserNotifSettingChannelDefault> { | 551 | static loadForMeAPI (username: string): Bluebird<MUserNotifSettingChannelDefault> { |
444 | const query = { | 552 | const query = { |
445 | where: { | 553 | where: { |
446 | username: { [ Op.iLike ]: username } | 554 | username: { [Op.iLike]: username } |
447 | } | 555 | } |
448 | } | 556 | } |
449 | 557 | ||
@@ -465,7 +573,7 @@ export class UserModel extends Model<UserModel> { | |||
465 | 573 | ||
466 | const query = { | 574 | const query = { |
467 | where: { | 575 | where: { |
468 | [ Op.or ]: [ | 576 | [Op.or]: [ |
469 | where(fn('lower', col('username')), fn('lower', username)), | 577 | where(fn('lower', col('username')), fn('lower', username)), |
470 | 578 | ||
471 | { email } | 579 | { email } |
@@ -567,7 +675,10 @@ export class UserModel extends Model<UserModel> { | |||
567 | 675 | ||
568 | static getOriginalVideoFileTotalFromUser (user: MUserId) { | 676 | static getOriginalVideoFileTotalFromUser (user: MUserId) { |
569 | // Don't use sequelize because we need to use a sub query | 677 | // Don't use sequelize because we need to use a sub query |
570 | const query = UserModel.generateUserQuotaBaseSQL() | 678 | const query = UserModel.generateUserQuotaBaseSQL({ |
679 | withSelect: true, | ||
680 | whereUserId: '$userId' | ||
681 | }) | ||
571 | 682 | ||
572 | return UserModel.getTotalRawQuery(query, user.id) | 683 | return UserModel.getTotalRawQuery(query, user.id) |
573 | } | 684 | } |
@@ -575,16 +686,38 @@ export class UserModel extends Model<UserModel> { | |||
575 | // Returns cumulative size of all video files uploaded in the last 24 hours. | 686 | // Returns cumulative size of all video files uploaded in the last 24 hours. |
576 | static getOriginalVideoFileTotalDailyFromUser (user: MUserId) { | 687 | static getOriginalVideoFileTotalDailyFromUser (user: MUserId) { |
577 | // Don't use sequelize because we need to use a sub query | 688 | // Don't use sequelize because we need to use a sub query |
578 | const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'') | 689 | const query = UserModel.generateUserQuotaBaseSQL({ |
690 | withSelect: true, | ||
691 | whereUserId: '$userId', | ||
692 | where: '"video"."createdAt" > now() - interval \'24 hours\'' | ||
693 | }) | ||
579 | 694 | ||
580 | return UserModel.getTotalRawQuery(query, user.id) | 695 | return UserModel.getTotalRawQuery(query, user.id) |
581 | } | 696 | } |
582 | 697 | ||
583 | static async getStats () { | 698 | static async getStats () { |
699 | function getActiveUsers (days: number) { | ||
700 | const query = { | ||
701 | where: { | ||
702 | [Op.and]: [ | ||
703 | literal(`"lastLoginDate" > NOW() - INTERVAL '${days}d'`) | ||
704 | ] | ||
705 | } | ||
706 | } | ||
707 | |||
708 | return UserModel.count(query) | ||
709 | } | ||
710 | |||
584 | const totalUsers = await UserModel.count() | 711 | const totalUsers = await UserModel.count() |
712 | const totalDailyActiveUsers = await getActiveUsers(1) | ||
713 | const totalWeeklyActiveUsers = await getActiveUsers(7) | ||
714 | const totalMonthlyActiveUsers = await getActiveUsers(30) | ||
585 | 715 | ||
586 | return { | 716 | return { |
587 | totalUsers | 717 | totalUsers, |
718 | totalDailyActiveUsers, | ||
719 | totalWeeklyActiveUsers, | ||
720 | totalMonthlyActiveUsers | ||
588 | } | 721 | } |
589 | } | 722 | } |
590 | 723 | ||
@@ -592,7 +725,7 @@ export class UserModel extends Model<UserModel> { | |||
592 | const query = { | 725 | const query = { |
593 | where: { | 726 | where: { |
594 | username: { | 727 | username: { |
595 | [ Op.like ]: `%${search}%` | 728 | [Op.like]: `%${search}%` |
596 | } | 729 | } |
597 | }, | 730 | }, |
598 | limit: 10 | 731 | limit: 10 |
@@ -633,6 +766,10 @@ export class UserModel extends Model<UserModel> { | |||
633 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { | 766 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { |
634 | const videoQuotaUsed = this.get('videoQuotaUsed') | 767 | const videoQuotaUsed = this.get('videoQuotaUsed') |
635 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') | 768 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') |
769 | const videosCount = this.get('videosCount') | ||
770 | const [ videoAbusesCount, videoAbusesAcceptedCount ] = (this.get('videoAbusesCount') as string || ':').split(':') | ||
771 | const videoAbusesCreatedCount = this.get('videoAbusesCreatedCount') | ||
772 | const videoCommentsCount = this.get('videoCommentsCount') | ||
636 | 773 | ||
637 | const json: User = { | 774 | const json: User = { |
638 | id: this.id, | 775 | id: this.id, |
@@ -652,7 +789,7 @@ export class UserModel extends Model<UserModel> { | |||
652 | videoLanguages: this.videoLanguages, | 789 | videoLanguages: this.videoLanguages, |
653 | 790 | ||
654 | role: this.role, | 791 | role: this.role, |
655 | roleLabel: USER_ROLE_LABELS[ this.role ], | 792 | roleLabel: USER_ROLE_LABELS[this.role], |
656 | 793 | ||
657 | videoQuota: this.videoQuota, | 794 | videoQuota: this.videoQuota, |
658 | videoQuotaDaily: this.videoQuotaDaily, | 795 | videoQuotaDaily: this.videoQuotaDaily, |
@@ -662,6 +799,21 @@ export class UserModel extends Model<UserModel> { | |||
662 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined | 799 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined |
663 | ? parseInt(videoQuotaUsedDaily + '', 10) | 800 | ? parseInt(videoQuotaUsedDaily + '', 10) |
664 | : undefined, | 801 | : undefined, |
802 | videosCount: videosCount !== undefined | ||
803 | ? parseInt(videosCount + '', 10) | ||
804 | : undefined, | ||
805 | videoAbusesCount: videoAbusesCount | ||
806 | ? parseInt(videoAbusesCount, 10) | ||
807 | : undefined, | ||
808 | videoAbusesAcceptedCount: videoAbusesAcceptedCount | ||
809 | ? parseInt(videoAbusesAcceptedCount, 10) | ||
810 | : undefined, | ||
811 | videoAbusesCreatedCount: videoAbusesCreatedCount !== undefined | ||
812 | ? parseInt(videoAbusesCreatedCount + '', 10) | ||
813 | : undefined, | ||
814 | videoCommentsCount: videoCommentsCount !== undefined | ||
815 | ? parseInt(videoCommentsCount + '', 10) | ||
816 | : undefined, | ||
665 | 817 | ||
666 | noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, | 818 | noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, |
667 | noWelcomeModal: this.noWelcomeModal, | 819 | noWelcomeModal: this.noWelcomeModal, |
@@ -677,7 +829,11 @@ export class UserModel extends Model<UserModel> { | |||
677 | 829 | ||
678 | videoChannels: [], | 830 | videoChannels: [], |
679 | 831 | ||
680 | createdAt: this.createdAt | 832 | createdAt: this.createdAt, |
833 | |||
834 | pluginAuth: this.pluginAuth, | ||
835 | |||
836 | lastLoginDate: this.lastLoginDate | ||
681 | } | 837 | } |
682 | 838 | ||
683 | if (parameters.withAdminFlags) { | 839 | if (parameters.withAdminFlags) { |
@@ -686,13 +842,13 @@ export class UserModel extends Model<UserModel> { | |||
686 | 842 | ||
687 | if (Array.isArray(this.Account.VideoChannels) === true) { | 843 | if (Array.isArray(this.Account.VideoChannels) === true) { |
688 | json.videoChannels = this.Account.VideoChannels | 844 | json.videoChannels = this.Account.VideoChannels |
689 | .map(c => c.toFormattedJSON()) | 845 | .map(c => c.toFormattedJSON()) |
690 | .sort((v1, v2) => { | 846 | .sort((v1, v2) => { |
691 | if (v1.createdAt < v2.createdAt) return -1 | 847 | if (v1.createdAt < v2.createdAt) return -1 |
692 | if (v1.createdAt === v2.createdAt) return 0 | 848 | if (v1.createdAt === v2.createdAt) return 0 |
693 | 849 | ||
694 | return 1 | 850 | return 1 |
695 | }) | 851 | }) |
696 | } | 852 | } |
697 | 853 | ||
698 | return json | 854 | return json |
@@ -702,7 +858,7 @@ export class UserModel extends Model<UserModel> { | |||
702 | const formatted = this.toFormattedJSON() | 858 | const formatted = this.toFormattedJSON() |
703 | 859 | ||
704 | const specialPlaylists = this.Account.VideoPlaylists | 860 | const specialPlaylists = this.Account.VideoPlaylists |
705 | .map(p => ({ id: p.id, name: p.name, type: p.type })) | 861 | .map(p => ({ id: p.id, name: p.name, type: p.type })) |
706 | 862 | ||
707 | return Object.assign(formatted, { specialPlaylists }) | 863 | return Object.assign(formatted, { specialPlaylists }) |
708 | } | 864 | } |
@@ -724,18 +880,33 @@ export class UserModel extends Model<UserModel> { | |||
724 | return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily | 880 | return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily |
725 | } | 881 | } |
726 | 882 | ||
727 | private static generateUserQuotaBaseSQL (where?: string) { | 883 | private static generateUserQuotaBaseSQL (options: { |
728 | const andWhere = where ? 'AND ' + where : '' | 884 | whereUserId: '$userId' | '"UserModel"."id"' |
729 | 885 | withSelect: boolean | |
730 | return 'SELECT SUM("size") AS "total" ' + | 886 | where?: string |
887 | }) { | ||
888 | const andWhere = options.where | ||
889 | ? 'AND ' + options.where | ||
890 | : '' | ||
891 | |||
892 | const videoChannelJoin = 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
893 | 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + | ||
894 | `WHERE "account"."userId" = ${options.whereUserId} ${andWhere}` | ||
895 | |||
896 | const webtorrentFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + | ||
897 | 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + | ||
898 | videoChannelJoin | ||
899 | |||
900 | const hlsFiles = 'SELECT "videoFile"."size" AS "size", "video"."id" AS "videoId" FROM "videoFile" ' + | ||
901 | 'INNER JOIN "videoStreamingPlaylist" ON "videoFile"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id ' + | ||
902 | 'INNER JOIN "video" ON "videoStreamingPlaylist"."videoId" = "video"."id" ' + | ||
903 | videoChannelJoin | ||
904 | |||
905 | return 'SELECT COALESCE(SUM("size"), 0) AS "total" ' + | ||
731 | 'FROM (' + | 906 | 'FROM (' + |
732 | 'SELECT MAX("videoFile"."size") AS "size" FROM "videoFile" ' + | 907 | `SELECT MAX("t1"."size") AS "size" FROM (${webtorrentFiles} UNION ${hlsFiles}) t1 ` + |
733 | 'INNER JOIN "video" ON "videoFile"."videoId" = "video"."id" ' + | 908 | 'GROUP BY "t1"."videoId"' + |
734 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | 909 | ') t2' |
735 | 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + | ||
736 | 'WHERE "account"."userId" = $userId ' + andWhere + | ||
737 | 'GROUP BY "video"."id"' + | ||
738 | ') t' | ||
739 | } | 910 | } |
740 | 911 | ||
741 | private static getTotalRawQuery (query: string, userId: number) { | 912 | private static getTotalRawQuery (query: string, userId: number) { |
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index f21d2b8a2..85a371026 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { values, difference } from 'lodash' | 2 | import { difference, values } from 'lodash' |
3 | import { | 3 | import { |
4 | AfterCreate, | 4 | AfterCreate, |
5 | AfterDestroy, | 5 | AfterDestroy, |
@@ -20,10 +20,9 @@ import { | |||
20 | import { FollowState } from '../../../shared/models/actors' | 20 | import { FollowState } from '../../../shared/models/actors' |
21 | import { ActorFollow } from '../../../shared/models/actors/follow.model' | 21 | import { ActorFollow } from '../../../shared/models/actors/follow.model' |
22 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
23 | import { getServerActor } from '../../helpers/utils' | ||
24 | import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants' | 23 | import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants' |
25 | import { ServerModel } from '../server/server' | 24 | import { ServerModel } from '../server/server' |
26 | import { createSafeIn, getSort, getFollowsSort } from '../utils' | 25 | import { createSafeIn, getFollowsSort, getSort } from '../utils' |
27 | import { ActorModel, unusedActorAttributesForAPI } from './actor' | 26 | import { ActorModel, unusedActorAttributesForAPI } from './actor' |
28 | import { VideoChannelModel } from '../video/video-channel' | 27 | import { VideoChannelModel } from '../video/video-channel' |
29 | import { AccountModel } from '../account/account' | 28 | import { AccountModel } from '../account/account' |
@@ -36,7 +35,8 @@ import { | |||
36 | MActorFollowSubscriptions | 35 | MActorFollowSubscriptions |
37 | } from '@server/typings/models' | 36 | } from '@server/typings/models' |
38 | import { ActivityPubActorType } from '@shared/models' | 37 | import { ActivityPubActorType } from '@shared/models' |
39 | import { afterCommitIfTransaction } from '@server/helpers/database-utils' | 38 | import { VideoModel } from '@server/models/video/video' |
39 | import { getServerActor } from '@server/models/application/application' | ||
40 | 40 | ||
41 | @Table({ | 41 | @Table({ |
42 | tableName: 'actorFollow', | 42 | tableName: 'actorFollow', |
@@ -152,6 +152,18 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
152 | if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) | 152 | if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) |
153 | } | 153 | } |
154 | 154 | ||
155 | static isFollowedBy (actorId: number, followerActorId: number) { | ||
156 | const query = 'SELECT 1 FROM "actorFollow" WHERE "actorId" = $followerActorId AND "targetActorId" = $actorId LIMIT 1' | ||
157 | const options = { | ||
158 | type: QueryTypes.SELECT as QueryTypes.SELECT, | ||
159 | bind: { actorId, followerActorId }, | ||
160 | raw: true | ||
161 | } | ||
162 | |||
163 | return VideoModel.sequelize.query(query, options) | ||
164 | .then(results => results.length === 1) | ||
165 | } | ||
166 | |||
155 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Bluebird<MActorFollowActorsDefault> { | 167 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Bluebird<MActorFollowActorsDefault> { |
156 | const query = { | 168 | const query = { |
157 | where: { | 169 | where: { |
@@ -226,7 +238,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
226 | 238 | ||
227 | return ActorFollowModel.findOne(query) | 239 | return ActorFollowModel.findOne(query) |
228 | .then(result => { | 240 | .then(result => { |
229 | if (result && result.ActorFollowing.VideoChannel) { | 241 | if (result?.ActorFollowing.VideoChannel) { |
230 | result.ActorFollowing.VideoChannel.Actor = result.ActorFollowing | 242 | result.ActorFollowing.VideoChannel.Actor = result.ActorFollowing |
231 | } | 243 | } |
232 | 244 | ||
@@ -239,24 +251,24 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
239 | .map(t => { | 251 | .map(t => { |
240 | if (t.host) { | 252 | if (t.host) { |
241 | return { | 253 | return { |
242 | [ Op.and ]: [ | 254 | [Op.and]: [ |
243 | { | 255 | { |
244 | '$preferredUsername$': t.name | 256 | $preferredUsername$: t.name |
245 | }, | 257 | }, |
246 | { | 258 | { |
247 | '$host$': t.host | 259 | $host$: t.host |
248 | } | 260 | } |
249 | ] | 261 | ] |
250 | } | 262 | } |
251 | } | 263 | } |
252 | 264 | ||
253 | return { | 265 | return { |
254 | [ Op.and ]: [ | 266 | [Op.and]: [ |
255 | { | 267 | { |
256 | '$preferredUsername$': t.name | 268 | $preferredUsername$: t.name |
257 | }, | 269 | }, |
258 | { | 270 | { |
259 | '$serverId$': null | 271 | $serverId$: null |
260 | } | 272 | } |
261 | ] | 273 | ] |
262 | } | 274 | } |
@@ -265,9 +277,9 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
265 | const query = { | 277 | const query = { |
266 | attributes: [], | 278 | attributes: [], |
267 | where: { | 279 | where: { |
268 | [ Op.and ]: [ | 280 | [Op.and]: [ |
269 | { | 281 | { |
270 | [ Op.or ]: whereTab | 282 | [Op.or]: whereTab |
271 | }, | 283 | }, |
272 | { | 284 | { |
273 | actorId | 285 | actorId |
@@ -295,12 +307,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
295 | } | 307 | } |
296 | 308 | ||
297 | static listFollowingForApi (options: { | 309 | static listFollowingForApi (options: { |
298 | id: number, | 310 | id: number |
299 | start: number, | 311 | start: number |
300 | count: number, | 312 | count: number |
301 | sort: string, | 313 | sort: string |
302 | state?: FollowState, | 314 | state?: FollowState |
303 | actorType?: ActivityPubActorType, | 315 | actorType?: ActivityPubActorType |
304 | search?: string | 316 | search?: string |
305 | }) { | 317 | }) { |
306 | const { id, start, count, sort, search, state, actorType } = options | 318 | const { id, start, count, sort, search, state, actorType } = options |
@@ -312,7 +324,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
312 | if (search) { | 324 | if (search) { |
313 | Object.assign(followingServerWhere, { | 325 | Object.assign(followingServerWhere, { |
314 | host: { | 326 | host: { |
315 | [ Op.iLike ]: '%' + search + '%' | 327 | [Op.iLike]: '%' + search + '%' |
316 | } | 328 | } |
317 | }) | 329 | }) |
318 | } | 330 | } |
@@ -362,12 +374,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
362 | } | 374 | } |
363 | 375 | ||
364 | static listFollowersForApi (options: { | 376 | static listFollowersForApi (options: { |
365 | actorId: number, | 377 | actorId: number |
366 | start: number, | 378 | start: number |
367 | count: number, | 379 | count: number |
368 | sort: string, | 380 | sort: string |
369 | state?: FollowState, | 381 | state?: FollowState |
370 | actorType?: ActivityPubActorType, | 382 | actorType?: ActivityPubActorType |
371 | search?: string | 383 | search?: string |
372 | }) { | 384 | }) { |
373 | const { actorId, start, count, sort, search, state, actorType } = options | 385 | const { actorId, start, count, sort, search, state, actorType } = options |
@@ -379,7 +391,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
379 | if (search) { | 391 | if (search) { |
380 | Object.assign(followerServerWhere, { | 392 | Object.assign(followerServerWhere, { |
381 | host: { | 393 | host: { |
382 | [ Op.iLike ]: '%' + search + '%' | 394 | [Op.iLike]: '%' + search + '%' |
383 | } | 395 | } |
384 | }) | 396 | }) |
385 | } | 397 | } |
@@ -631,7 +643,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
631 | 643 | ||
632 | const tasks: Bluebird<any>[] = [] | 644 | const tasks: Bluebird<any>[] = [] |
633 | 645 | ||
634 | for (let selection of selections) { | 646 | for (const selection of selections) { |
635 | let query = 'SELECT ' + selection + ' FROM "actor" ' + | 647 | let query = 'SELECT ' + selection + ' FROM "actor" ' + |
636 | 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' + | 648 | 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' + |
637 | 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' + | 649 | 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' + |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 007647ced..34bc91706 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -16,7 +16,7 @@ import { | |||
16 | Table, | 16 | Table, |
17 | UpdatedAt | 17 | UpdatedAt |
18 | } from 'sequelize-typescript' | 18 | } from 'sequelize-typescript' |
19 | import { ActivityPubActorType } from '../../../shared/models/activitypub' | 19 | import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' |
20 | import { Avatar } from '../../../shared/models/avatars/avatar.model' | 20 | import { Avatar } from '../../../shared/models/avatars/avatar.model' |
21 | import { activityPubContextify } from '../../helpers/activitypub' | 21 | import { activityPubContextify } from '../../helpers/activitypub' |
22 | import { | 22 | import { |
@@ -43,11 +43,12 @@ import { | |||
43 | MActorFull, | 43 | MActorFull, |
44 | MActorHost, | 44 | MActorHost, |
45 | MActorServer, | 45 | MActorServer, |
46 | MActorSummaryFormattable, | 46 | MActorSummaryFormattable, MActorUrl, |
47 | MActorWithInboxes | 47 | MActorWithInboxes |
48 | } from '../../typings/models' | 48 | } from '../../typings/models' |
49 | import * as Bluebird from 'bluebird' | 49 | import * as Bluebird from 'bluebird' |
50 | import { Op, Transaction, literal } from 'sequelize' | 50 | import { Op, Transaction, literal } from 'sequelize' |
51 | import { ModelCache } from '@server/models/model-cache' | ||
51 | 52 | ||
52 | enum ScopeNames { | 53 | enum ScopeNames { |
53 | FULL = 'FULL' | 54 | FULL = 'FULL' |
@@ -122,13 +123,13 @@ export const unusedActorAttributesForAPI = [ | |||
122 | } | 123 | } |
123 | } | 124 | } |
124 | }, | 125 | }, |
125 | // { | 126 | { |
126 | // fields: [ 'preferredUsername' ], | 127 | fields: [ 'preferredUsername' ], |
127 | // unique: true, | 128 | unique: true, |
128 | // where: { | 129 | where: { |
129 | // serverId: null | 130 | serverId: null |
130 | // } | 131 | } |
131 | // }, | 132 | }, |
132 | { | 133 | { |
133 | fields: [ 'inboxUrl', 'sharedInboxUrl' ] | 134 | fields: [ 'inboxUrl', 'sharedInboxUrl' ] |
134 | }, | 135 | }, |
@@ -276,8 +277,6 @@ export class ActorModel extends Model<ActorModel> { | |||
276 | }) | 277 | }) |
277 | VideoChannel: VideoChannelModel | 278 | VideoChannel: VideoChannelModel |
278 | 279 | ||
279 | private static cache: { [ id: string ]: any } = {} | ||
280 | |||
281 | static load (id: number): Bluebird<MActor> { | 280 | static load (id: number): Bluebird<MActor> { |
282 | return ActorModel.unscoped().findByPk(id) | 281 | return ActorModel.unscoped().findByPk(id) |
283 | } | 282 | } |
@@ -334,7 +333,7 @@ export class ActorModel extends Model<ActorModel> { | |||
334 | const query = { | 333 | const query = { |
335 | where: { | 334 | where: { |
336 | followersUrl: { | 335 | followersUrl: { |
337 | [ Op.in ]: followersUrls | 336 | [Op.in]: followersUrls |
338 | } | 337 | } |
339 | }, | 338 | }, |
340 | transaction | 339 | transaction |
@@ -344,28 +343,50 @@ export class ActorModel extends Model<ActorModel> { | |||
344 | } | 343 | } |
345 | 344 | ||
346 | static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> { | 345 | static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorFull> { |
347 | // The server actor never change, so we can easily cache it | 346 | const fun = () => { |
348 | if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.cache[preferredUsername]) { | 347 | const query = { |
349 | return Bluebird.resolve(ActorModel.cache[preferredUsername]) | 348 | where: { |
350 | } | 349 | preferredUsername, |
350 | serverId: null | ||
351 | }, | ||
352 | transaction | ||
353 | } | ||
351 | 354 | ||
352 | const query = { | 355 | return ActorModel.scope(ScopeNames.FULL) |
353 | where: { | 356 | .findOne(query) |
354 | preferredUsername, | ||
355 | serverId: null | ||
356 | }, | ||
357 | transaction | ||
358 | } | 357 | } |
359 | 358 | ||
360 | return ActorModel.scope(ScopeNames.FULL) | 359 | return ModelCache.Instance.doCache({ |
361 | .findOne(query) | 360 | cacheType: 'local-actor-name', |
362 | .then(actor => { | 361 | key: preferredUsername, |
363 | if (preferredUsername === SERVER_ACTOR_NAME) { | 362 | // The server actor never change, so we can easily cache it |
364 | ActorModel.cache[ preferredUsername ] = actor | 363 | whitelist: () => preferredUsername === SERVER_ACTOR_NAME, |
365 | } | 364 | fun |
365 | }) | ||
366 | } | ||
367 | |||
368 | static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird<MActorUrl> { | ||
369 | const fun = () => { | ||
370 | const query = { | ||
371 | attributes: [ 'url' ], | ||
372 | where: { | ||
373 | preferredUsername, | ||
374 | serverId: null | ||
375 | }, | ||
376 | transaction | ||
377 | } | ||
366 | 378 | ||
367 | return actor | 379 | return ActorModel.unscoped() |
368 | }) | 380 | .findOne(query) |
381 | } | ||
382 | |||
383 | return ModelCache.Instance.doCache({ | ||
384 | cacheType: 'local-actor-name', | ||
385 | key: preferredUsername, | ||
386 | // The server actor never change, so we can easily cache it | ||
387 | whitelist: () => preferredUsername === SERVER_ACTOR_NAME, | ||
388 | fun | ||
389 | }) | ||
369 | } | 390 | } |
370 | 391 | ||
371 | static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> { | 392 | static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> { |
@@ -441,6 +462,36 @@ export class ActorModel extends Model<ActorModel> { | |||
441 | }, { where, transaction }) | 462 | }, { where, transaction }) |
442 | } | 463 | } |
443 | 464 | ||
465 | static loadAccountActorByVideoId (videoId: number): Bluebird<MActor> { | ||
466 | const query = { | ||
467 | include: [ | ||
468 | { | ||
469 | attributes: [ 'id' ], | ||
470 | model: AccountModel.unscoped(), | ||
471 | required: true, | ||
472 | include: [ | ||
473 | { | ||
474 | attributes: [ 'id', 'accountId' ], | ||
475 | model: VideoChannelModel.unscoped(), | ||
476 | required: true, | ||
477 | include: [ | ||
478 | { | ||
479 | attributes: [ 'id', 'channelId' ], | ||
480 | model: VideoModel.unscoped(), | ||
481 | where: { | ||
482 | id: videoId | ||
483 | } | ||
484 | } | ||
485 | ] | ||
486 | } | ||
487 | ] | ||
488 | } | ||
489 | ] | ||
490 | } | ||
491 | |||
492 | return ActorModel.unscoped().findOne(query) | ||
493 | } | ||
494 | |||
444 | getSharedInbox (this: MActorWithInboxes) { | 495 | getSharedInbox (this: MActorWithInboxes) { |
445 | return this.sharedInboxUrl || this.inboxUrl | 496 | return this.sharedInboxUrl || this.inboxUrl |
446 | } | 497 | } |
@@ -473,9 +524,11 @@ export class ActorModel extends Model<ActorModel> { | |||
473 | } | 524 | } |
474 | 525 | ||
475 | toActivityPubObject (this: MActorAP, name: string) { | 526 | toActivityPubObject (this: MActorAP, name: string) { |
476 | let icon = undefined | 527 | let icon: ActivityIconObject |
528 | |||
477 | if (this.avatarId) { | 529 | if (this.avatarId) { |
478 | const extension = extname(this.Avatar.filename) | 530 | const extension = extname(this.Avatar.filename) |
531 | |||
479 | icon = { | 532 | icon = { |
480 | type: 'Image', | 533 | type: 'Image', |
481 | mediaType: extension === '.png' ? 'image/png' : 'image/jpeg', | 534 | mediaType: extension === '.png' ? 'image/png' : 'image/jpeg', |
diff --git a/server/models/application/application.ts b/server/models/application/application.ts index 81320b9af..3bba2c70e 100644 --- a/server/models/application/application.ts +++ b/server/models/application/application.ts | |||
@@ -1,5 +1,16 @@ | |||
1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' | 1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' |
2 | import { AccountModel } from '../account/account' | 2 | import { AccountModel } from '../account/account' |
3 | import * as memoizee from 'memoizee' | ||
4 | |||
5 | export const getServerActor = memoizee(async function () { | ||
6 | const application = await ApplicationModel.load() | ||
7 | if (!application) throw Error('Could not load Application from database.') | ||
8 | |||
9 | const actor = application.Account.Actor | ||
10 | actor.Account = application.Account | ||
11 | |||
12 | return actor | ||
13 | }, { promise: true }) | ||
3 | 14 | ||
4 | @DefaultScope(() => ({ | 15 | @DefaultScope(() => ({ |
5 | include: [ | 16 | include: [ |
diff --git a/server/models/model-cache.ts b/server/models/model-cache.ts new file mode 100644 index 000000000..a87f99aa2 --- /dev/null +++ b/server/models/model-cache.ts | |||
@@ -0,0 +1,91 @@ | |||
1 | import { Model } from 'sequelize-typescript' | ||
2 | import * as Bluebird from 'bluebird' | ||
3 | import { logger } from '@server/helpers/logger' | ||
4 | |||
5 | type ModelCacheType = | ||
6 | 'local-account-name' | ||
7 | | 'local-actor-name' | ||
8 | | 'local-actor-url' | ||
9 | | 'load-video-immutable-id' | ||
10 | | 'load-video-immutable-url' | ||
11 | |||
12 | type DeleteKey = | ||
13 | 'video' | ||
14 | |||
15 | class ModelCache { | ||
16 | |||
17 | private static instance: ModelCache | ||
18 | |||
19 | private readonly localCache: { [id in ModelCacheType]: Map<string, any> } = { | ||
20 | 'local-account-name': new Map(), | ||
21 | 'local-actor-name': new Map(), | ||
22 | 'local-actor-url': new Map(), | ||
23 | 'load-video-immutable-id': new Map(), | ||
24 | 'load-video-immutable-url': new Map() | ||
25 | } | ||
26 | |||
27 | private readonly deleteIds: { | ||
28 | [deleteKey in DeleteKey]: Map<number, { cacheType: ModelCacheType, key: string }[]> | ||
29 | } = { | ||
30 | video: new Map() | ||
31 | } | ||
32 | |||
33 | private constructor () { | ||
34 | } | ||
35 | |||
36 | static get Instance () { | ||
37 | return this.instance || (this.instance = new this()) | ||
38 | } | ||
39 | |||
40 | doCache<T extends Model> (options: { | ||
41 | cacheType: ModelCacheType | ||
42 | key: string | ||
43 | fun: () => Bluebird<T> | ||
44 | whitelist?: () => boolean | ||
45 | deleteKey?: DeleteKey | ||
46 | }) { | ||
47 | const { cacheType, key, fun, whitelist, deleteKey } = options | ||
48 | |||
49 | if (whitelist && whitelist() !== true) return fun() | ||
50 | |||
51 | const cache = this.localCache[cacheType] | ||
52 | |||
53 | if (cache.has(key)) { | ||
54 | logger.debug('Model cache hit for %s -> %s.', cacheType, key) | ||
55 | return Bluebird.resolve<T>(cache.get(key)) | ||
56 | } | ||
57 | |||
58 | return fun().then(m => { | ||
59 | if (!m) return m | ||
60 | |||
61 | if (!whitelist || whitelist()) cache.set(key, m) | ||
62 | |||
63 | if (deleteKey) { | ||
64 | const map = this.deleteIds[deleteKey] | ||
65 | if (!map.has(m.id)) map.set(m.id, []) | ||
66 | |||
67 | const a = map.get(m.id) | ||
68 | a.push({ cacheType, key }) | ||
69 | } | ||
70 | |||
71 | return m | ||
72 | }) | ||
73 | } | ||
74 | |||
75 | invalidateCache (deleteKey: DeleteKey, modelId: number) { | ||
76 | const map = this.deleteIds[deleteKey] | ||
77 | |||
78 | if (!map.has(modelId)) return | ||
79 | |||
80 | for (const toDelete of map.get(modelId)) { | ||
81 | logger.debug('Removing %s -> %d of model cache %s -> %s.', deleteKey, modelId, toDelete.cacheType, toDelete.key) | ||
82 | this.localCache[toDelete.cacheType].delete(toDelete.key) | ||
83 | } | ||
84 | |||
85 | map.delete(modelId) | ||
86 | } | ||
87 | } | ||
88 | |||
89 | export { | ||
90 | ModelCache | ||
91 | } | ||
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index b680be237..38953e8ad 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -23,13 +23,14 @@ import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | |||
23 | 23 | ||
24 | export type OAuthTokenInfo = { | 24 | export type OAuthTokenInfo = { |
25 | refreshToken: string | 25 | refreshToken: string |
26 | refreshTokenExpiresAt: Date, | 26 | refreshTokenExpiresAt: Date |
27 | client: { | 27 | client: { |
28 | id: number | 28 | id: number |
29 | }, | 29 | } |
30 | user: { | 30 | user: { |
31 | id: number | 31 | id: number |
32 | } | 32 | } |
33 | token: MOAuthTokenUser | ||
33 | } | 34 | } |
34 | 35 | ||
35 | enum ScopeNames { | 36 | enum ScopeNames { |
@@ -97,6 +98,9 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
97 | @Column | 98 | @Column |
98 | refreshTokenExpiresAt: Date | 99 | refreshTokenExpiresAt: Date |
99 | 100 | ||
101 | @Column | ||
102 | authName: string | ||
103 | |||
100 | @CreatedAt | 104 | @CreatedAt |
101 | createdAt: Date | 105 | createdAt: Date |
102 | 106 | ||
@@ -133,33 +137,41 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
133 | return clearCacheByToken(token.accessToken) | 137 | return clearCacheByToken(token.accessToken) |
134 | } | 138 | } |
135 | 139 | ||
140 | static loadByRefreshToken (refreshToken: string) { | ||
141 | const query = { | ||
142 | where: { refreshToken } | ||
143 | } | ||
144 | |||
145 | return OAuthTokenModel.findOne(query) | ||
146 | } | ||
147 | |||
136 | static getByRefreshTokenAndPopulateClient (refreshToken: string) { | 148 | static getByRefreshTokenAndPopulateClient (refreshToken: string) { |
137 | const query = { | 149 | const query = { |
138 | where: { | 150 | where: { |
139 | refreshToken: refreshToken | 151 | refreshToken |
140 | }, | 152 | }, |
141 | include: [ OAuthClientModel ] | 153 | include: [ OAuthClientModel ] |
142 | } | 154 | } |
143 | 155 | ||
144 | return OAuthTokenModel.findOne(query) | 156 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) |
145 | .then(token => { | 157 | .findOne(query) |
146 | if (!token) return null | 158 | .then(token => { |
147 | 159 | if (!token) return null | |
148 | return { | 160 | |
149 | refreshToken: token.refreshToken, | 161 | return { |
150 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, | 162 | refreshToken: token.refreshToken, |
151 | client: { | 163 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, |
152 | id: token.oAuthClientId | 164 | client: { |
153 | }, | 165 | id: token.oAuthClientId |
154 | user: { | 166 | }, |
155 | id: token.userId | 167 | user: token.User, |
156 | } | 168 | token |
157 | } as OAuthTokenInfo | 169 | } as OAuthTokenInfo |
158 | }) | 170 | }) |
159 | .catch(err => { | 171 | .catch(err => { |
160 | logger.error('getRefreshToken error.', { err }) | 172 | logger.error('getRefreshToken error.', { err }) |
161 | throw err | 173 | throw err |
162 | }) | 174 | }) |
163 | } | 175 | } |
164 | 176 | ||
165 | static getByTokenAndPopulateUser (bearerToken: string): Bluebird<MOAuthTokenUser> { | 177 | static getByTokenAndPopulateUser (bearerToken: string): Bluebird<MOAuthTokenUser> { |
@@ -181,14 +193,14 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
181 | static getByRefreshTokenAndPopulateUser (refreshToken: string): Bluebird<MOAuthTokenUser> { | 193 | static getByRefreshTokenAndPopulateUser (refreshToken: string): Bluebird<MOAuthTokenUser> { |
182 | const query = { | 194 | const query = { |
183 | where: { | 195 | where: { |
184 | refreshToken: refreshToken | 196 | refreshToken |
185 | } | 197 | } |
186 | } | 198 | } |
187 | 199 | ||
188 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) | 200 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) |
189 | .findOne(query) | 201 | .findOne(query) |
190 | .then(token => { | 202 | .then(token => { |
191 | if (!token) return new OAuthTokenModel() | 203 | if (!token) return undefined |
192 | 204 | ||
193 | return Object.assign(token, { user: token.User }) | 205 | return Object.assign(token, { user: token.User }) |
194 | }) | 206 | }) |
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 8c9a7eabf..6021408bf 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -13,13 +13,12 @@ import { | |||
13 | UpdatedAt | 13 | UpdatedAt |
14 | } from 'sequelize-typescript' | 14 | } from 'sequelize-typescript' |
15 | import { ActorModel } from '../activitypub/actor' | 15 | import { ActorModel } from '../activitypub/actor' |
16 | import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' | 16 | import { getSort, getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' |
17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
18 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' | 18 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' |
19 | import { VideoFileModel } from '../video/video-file' | 19 | import { VideoFileModel } from '../video/video-file' |
20 | import { getServerActor } from '../../helpers/utils' | ||
21 | import { VideoModel } from '../video/video' | 20 | import { VideoModel } from '../video/video' |
22 | import { VideoRedundancyStrategy } from '../../../shared/models/redundancy' | 21 | import { VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../shared/models/redundancy' |
23 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
24 | import { CacheFileObject, VideoPrivacy } from '../../../shared' | 23 | import { CacheFileObject, VideoPrivacy } from '../../../shared' |
25 | import { VideoChannelModel } from '../video/video-channel' | 24 | import { VideoChannelModel } from '../video/video-channel' |
@@ -27,17 +26,24 @@ import { ServerModel } from '../server/server' | |||
27 | import { sample } from 'lodash' | 26 | import { sample } from 'lodash' |
28 | import { isTestInstance } from '../../helpers/core-utils' | 27 | import { isTestInstance } from '../../helpers/core-utils' |
29 | import * as Bluebird from 'bluebird' | 28 | import * as Bluebird from 'bluebird' |
30 | import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' | 29 | import { col, FindOptions, fn, literal, Op, Transaction, WhereOptions } from 'sequelize' |
31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' | 30 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' |
32 | import { CONFIG } from '../../initializers/config' | 31 | import { CONFIG } from '../../initializers/config' |
33 | import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models' | 32 | import { MVideoForRedundancyAPI, MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models' |
33 | import { VideoRedundanciesTarget } from '@shared/models/redundancy/video-redundancies-filters.model' | ||
34 | import { | ||
35 | FileRedundancyInformation, | ||
36 | StreamingPlaylistRedundancyInformation, | ||
37 | VideoRedundancy | ||
38 | } from '@shared/models/redundancy/video-redundancy.model' | ||
39 | import { getServerActor } from '@server/models/application/application' | ||
34 | 40 | ||
35 | export enum ScopeNames { | 41 | export enum ScopeNames { |
36 | WITH_VIDEO = 'WITH_VIDEO' | 42 | WITH_VIDEO = 'WITH_VIDEO' |
37 | } | 43 | } |
38 | 44 | ||
39 | @Scopes(() => ({ | 45 | @Scopes(() => ({ |
40 | [ ScopeNames.WITH_VIDEO ]: { | 46 | [ScopeNames.WITH_VIDEO]: { |
41 | include: [ | 47 | include: [ |
42 | { | 48 | { |
43 | model: VideoFileModel, | 49 | model: VideoFileModel, |
@@ -86,7 +92,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
86 | @UpdatedAt | 92 | @UpdatedAt |
87 | updatedAt: Date | 93 | updatedAt: Date |
88 | 94 | ||
89 | @AllowNull(false) | 95 | @AllowNull(true) |
90 | @Column | 96 | @Column |
91 | expiresOn: Date | 97 | expiresOn: Date |
92 | 98 | ||
@@ -161,7 +167,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
161 | logger.info('Removing duplicated video streaming playlist %s.', videoUUID) | 167 | logger.info('Removing duplicated video streaming playlist %s.', videoUUID) |
162 | 168 | ||
163 | videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true) | 169 | videoStreamingPlaylist.Video.removeStreamingPlaylistFiles(videoStreamingPlaylist, true) |
164 | .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err })) | 170 | .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err })) |
165 | } | 171 | } |
166 | 172 | ||
167 | return undefined | 173 | return undefined |
@@ -193,6 +199,15 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
193 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | 199 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) |
194 | } | 200 | } |
195 | 201 | ||
202 | static loadByIdWithVideo (id: number, transaction?: Transaction): Bluebird<MVideoRedundancyVideo> { | ||
203 | const query = { | ||
204 | where: { id }, | ||
205 | transaction | ||
206 | } | ||
207 | |||
208 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | ||
209 | } | ||
210 | |||
196 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> { | 211 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> { |
197 | const query = { | 212 | const query = { |
198 | where: { | 213 | where: { |
@@ -215,12 +230,12 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
215 | }, | 230 | }, |
216 | include: [ | 231 | include: [ |
217 | { | 232 | { |
218 | attributes: [ ], | 233 | attributes: [], |
219 | model: VideoFileModel, | 234 | model: VideoFileModel, |
220 | required: true, | 235 | required: true, |
221 | include: [ | 236 | include: [ |
222 | { | 237 | { |
223 | attributes: [ ], | 238 | attributes: [], |
224 | model: VideoModel, | 239 | model: VideoModel, |
225 | required: true, | 240 | required: true, |
226 | where: { | 241 | where: { |
@@ -233,7 +248,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
233 | } | 248 | } |
234 | 249 | ||
235 | return VideoRedundancyModel.findOne(query) | 250 | return VideoRedundancyModel.findOne(query) |
236 | .then(r => !!r) | 251 | .then(r => !!r) |
237 | } | 252 | } |
238 | 253 | ||
239 | static async getVideoSample (p: Bluebird<VideoModel[]>) { | 254 | static async getVideoSample (p: Bluebird<VideoModel[]>) { |
@@ -295,7 +310,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
295 | where: { | 310 | where: { |
296 | privacy: VideoPrivacy.PUBLIC, | 311 | privacy: VideoPrivacy.PUBLIC, |
297 | views: { | 312 | views: { |
298 | [ Op.gte ]: minViews | 313 | [Op.gte]: minViews |
299 | } | 314 | } |
300 | }, | 315 | }, |
301 | include: [ | 316 | include: [ |
@@ -318,7 +333,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
318 | actorId: actor.id, | 333 | actorId: actor.id, |
319 | strategy, | 334 | strategy, |
320 | createdAt: { | 335 | createdAt: { |
321 | [ Op.lt ]: expiredDate | 336 | [Op.lt]: expiredDate |
322 | } | 337 | } |
323 | } | 338 | } |
324 | } | 339 | } |
@@ -377,7 +392,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
377 | where: { | 392 | where: { |
378 | actorId: actor.id, | 393 | actorId: actor.id, |
379 | expiresOn: { | 394 | expiresOn: { |
380 | [ Op.lt ]: new Date() | 395 | [Op.lt]: new Date() |
381 | } | 396 | } |
382 | } | 397 | } |
383 | } | 398 | } |
@@ -394,7 +409,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
394 | [Op.ne]: actor.id | 409 | [Op.ne]: actor.id |
395 | }, | 410 | }, |
396 | expiresOn: { | 411 | expiresOn: { |
397 | [ Op.lt ]: new Date() | 412 | [Op.lt]: new Date(), |
413 | [Op.ne]: null | ||
398 | } | 414 | } |
399 | } | 415 | } |
400 | } | 416 | } |
@@ -447,7 +463,112 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
447 | return VideoRedundancyModel.findAll(query) | 463 | return VideoRedundancyModel.findAll(query) |
448 | } | 464 | } |
449 | 465 | ||
450 | static async getStats (strategy: VideoRedundancyStrategy) { | 466 | static listForApi (options: { |
467 | start: number | ||
468 | count: number | ||
469 | sort: string | ||
470 | target: VideoRedundanciesTarget | ||
471 | strategy?: string | ||
472 | }) { | ||
473 | const { start, count, sort, target, strategy } = options | ||
474 | const redundancyWhere: WhereOptions = {} | ||
475 | const videosWhere: WhereOptions = {} | ||
476 | let redundancySqlSuffix = '' | ||
477 | |||
478 | if (target === 'my-videos') { | ||
479 | Object.assign(videosWhere, { remote: false }) | ||
480 | } else if (target === 'remote-videos') { | ||
481 | Object.assign(videosWhere, { remote: true }) | ||
482 | Object.assign(redundancyWhere, { strategy: { [Op.ne]: null } }) | ||
483 | redundancySqlSuffix = ' AND "videoRedundancy"."strategy" IS NOT NULL' | ||
484 | } | ||
485 | |||
486 | if (strategy) { | ||
487 | Object.assign(redundancyWhere, { strategy: strategy }) | ||
488 | } | ||
489 | |||
490 | const videoFilterWhere = { | ||
491 | [Op.and]: [ | ||
492 | { | ||
493 | [Op.or]: [ | ||
494 | { | ||
495 | id: { | ||
496 | [Op.in]: literal( | ||
497 | '(' + | ||
498 | 'SELECT "videoId" FROM "videoFile" ' + | ||
499 | 'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoFileId" = "videoFile".id' + | ||
500 | redundancySqlSuffix + | ||
501 | ')' | ||
502 | ) | ||
503 | } | ||
504 | }, | ||
505 | { | ||
506 | id: { | ||
507 | [Op.in]: literal( | ||
508 | '(' + | ||
509 | 'select "videoId" FROM "videoStreamingPlaylist" ' + | ||
510 | 'INNER JOIN "videoRedundancy" ON "videoRedundancy"."videoStreamingPlaylistId" = "videoStreamingPlaylist".id' + | ||
511 | redundancySqlSuffix + | ||
512 | ')' | ||
513 | ) | ||
514 | } | ||
515 | } | ||
516 | ] | ||
517 | }, | ||
518 | |||
519 | videosWhere | ||
520 | ] | ||
521 | } | ||
522 | |||
523 | // /!\ On video model /!\ | ||
524 | const findOptions = { | ||
525 | offset: start, | ||
526 | limit: count, | ||
527 | order: getSort(sort), | ||
528 | include: [ | ||
529 | { | ||
530 | required: false, | ||
531 | model: VideoFileModel, | ||
532 | include: [ | ||
533 | { | ||
534 | model: VideoRedundancyModel.unscoped(), | ||
535 | required: false, | ||
536 | where: redundancyWhere | ||
537 | } | ||
538 | ] | ||
539 | }, | ||
540 | { | ||
541 | required: false, | ||
542 | model: VideoStreamingPlaylistModel.unscoped(), | ||
543 | include: [ | ||
544 | { | ||
545 | model: VideoRedundancyModel.unscoped(), | ||
546 | required: false, | ||
547 | where: redundancyWhere | ||
548 | }, | ||
549 | { | ||
550 | model: VideoFileModel, | ||
551 | required: false | ||
552 | } | ||
553 | ] | ||
554 | } | ||
555 | ], | ||
556 | where: videoFilterWhere | ||
557 | } | ||
558 | |||
559 | // /!\ On video model /!\ | ||
560 | const countOptions = { | ||
561 | where: videoFilterWhere | ||
562 | } | ||
563 | |||
564 | return Promise.all([ | ||
565 | VideoModel.findAll(findOptions), | ||
566 | |||
567 | VideoModel.count(countOptions) | ||
568 | ]).then(([ data, total ]) => ({ total, data })) | ||
569 | } | ||
570 | |||
571 | static async getStats (strategy: VideoRedundancyStrategyWithManual) { | ||
451 | const actor = await getServerActor() | 572 | const actor = await getServerActor() |
452 | 573 | ||
453 | const query: FindOptions = { | 574 | const query: FindOptions = { |
@@ -471,11 +592,58 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
471 | } | 592 | } |
472 | 593 | ||
473 | return VideoRedundancyModel.findOne(query) | 594 | return VideoRedundancyModel.findOne(query) |
474 | .then((r: any) => ({ | 595 | .then((r: any) => ({ |
475 | totalUsed: parseAggregateResult(r.totalUsed), | 596 | totalUsed: parseAggregateResult(r.totalUsed), |
476 | totalVideos: r.totalVideos, | 597 | totalVideos: r.totalVideos, |
477 | totalVideoFiles: r.totalVideoFiles | 598 | totalVideoFiles: r.totalVideoFiles |
478 | })) | 599 | })) |
600 | } | ||
601 | |||
602 | static toFormattedJSONStatic (video: MVideoForRedundancyAPI): VideoRedundancy { | ||
603 | const filesRedundancies: FileRedundancyInformation[] = [] | ||
604 | const streamingPlaylistsRedundancies: StreamingPlaylistRedundancyInformation[] = [] | ||
605 | |||
606 | for (const file of video.VideoFiles) { | ||
607 | for (const redundancy of file.RedundancyVideos) { | ||
608 | filesRedundancies.push({ | ||
609 | id: redundancy.id, | ||
610 | fileUrl: redundancy.fileUrl, | ||
611 | strategy: redundancy.strategy, | ||
612 | createdAt: redundancy.createdAt, | ||
613 | updatedAt: redundancy.updatedAt, | ||
614 | expiresOn: redundancy.expiresOn, | ||
615 | size: file.size | ||
616 | }) | ||
617 | } | ||
618 | } | ||
619 | |||
620 | for (const playlist of video.VideoStreamingPlaylists) { | ||
621 | const size = playlist.VideoFiles.reduce((a, b) => a + b.size, 0) | ||
622 | |||
623 | for (const redundancy of playlist.RedundancyVideos) { | ||
624 | streamingPlaylistsRedundancies.push({ | ||
625 | id: redundancy.id, | ||
626 | fileUrl: redundancy.fileUrl, | ||
627 | strategy: redundancy.strategy, | ||
628 | createdAt: redundancy.createdAt, | ||
629 | updatedAt: redundancy.updatedAt, | ||
630 | expiresOn: redundancy.expiresOn, | ||
631 | size | ||
632 | }) | ||
633 | } | ||
634 | } | ||
635 | |||
636 | return { | ||
637 | id: video.id, | ||
638 | name: video.name, | ||
639 | url: video.url, | ||
640 | uuid: video.uuid, | ||
641 | |||
642 | redundancies: { | ||
643 | files: filesRedundancies, | ||
644 | streamingPlaylists: streamingPlaylistsRedundancies | ||
645 | } | ||
646 | } | ||
479 | } | 647 | } |
480 | 648 | ||
481 | getVideo () { | 649 | getVideo () { |
@@ -494,7 +662,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
494 | id: this.url, | 662 | id: this.url, |
495 | type: 'CacheFile' as 'CacheFile', | 663 | type: 'CacheFile' as 'CacheFile', |
496 | object: this.VideoStreamingPlaylist.Video.url, | 664 | object: this.VideoStreamingPlaylist.Video.url, |
497 | expires: this.expiresOn.toISOString(), | 665 | expires: this.expiresOn ? this.expiresOn.toISOString() : null, |
498 | url: { | 666 | url: { |
499 | type: 'Link', | 667 | type: 'Link', |
500 | mediaType: 'application/x-mpegURL', | 668 | mediaType: 'application/x-mpegURL', |
@@ -507,10 +675,10 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
507 | id: this.url, | 675 | id: this.url, |
508 | type: 'CacheFile' as 'CacheFile', | 676 | type: 'CacheFile' as 'CacheFile', |
509 | object: this.VideoFile.Video.url, | 677 | object: this.VideoFile.Video.url, |
510 | expires: this.expiresOn.toISOString(), | 678 | expires: this.expiresOn ? this.expiresOn.toISOString() : null, |
511 | url: { | 679 | url: { |
512 | type: 'Link', | 680 | type: 'Link', |
513 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ this.VideoFile.extname ] as any, | 681 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[this.VideoFile.extname] as any, |
514 | href: this.fileUrl, | 682 | href: this.fileUrl, |
515 | height: this.VideoFile.resolution, | 683 | height: this.VideoFile.resolution, |
516 | size: this.VideoFile.size, | 684 | size: this.VideoFile.size, |
@@ -525,17 +693,17 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
525 | 693 | ||
526 | const notIn = literal( | 694 | const notIn = literal( |
527 | '(' + | 695 | '(' + |
528 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + | 696 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + |
529 | ')' | 697 | ')' |
530 | ) | 698 | ) |
531 | 699 | ||
532 | return { | 700 | return { |
533 | attributes: [], | 701 | attributes: [], |
534 | model: VideoFileModel.unscoped(), | 702 | model: VideoFileModel, |
535 | required: true, | 703 | required: true, |
536 | where: { | 704 | where: { |
537 | id: { | 705 | id: { |
538 | [ Op.notIn ]: notIn | 706 | [Op.notIn]: notIn |
539 | } | 707 | } |
540 | } | 708 | } |
541 | } | 709 | } |
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts index d094da1f5..53b6227d7 100644 --- a/server/models/server/plugin.ts +++ b/server/models/server/plugin.ts | |||
@@ -1,5 +1,10 @@ | |||
1 | import * as Bluebird from 'bluebird' | ||
2 | import { FindAndCountOptions, json, QueryTypes } from 'sequelize' | ||
1 | import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { AllowNull, Column, CreatedAt, DataType, DefaultScope, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { getSort, throwIfNotValid } from '../utils' | 4 | import { MPlugin, MPluginFormattable } from '@server/typings/models' |
5 | import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' | ||
6 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | ||
7 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | ||
3 | import { | 8 | import { |
4 | isPluginDescriptionValid, | 9 | isPluginDescriptionValid, |
5 | isPluginHomepage, | 10 | isPluginHomepage, |
@@ -7,12 +12,7 @@ import { | |||
7 | isPluginTypeValid, | 12 | isPluginTypeValid, |
8 | isPluginVersionValid | 13 | isPluginVersionValid |
9 | } from '../../helpers/custom-validators/plugins' | 14 | } from '../../helpers/custom-validators/plugins' |
10 | import { PluginType } from '../../../shared/models/plugins/plugin.type' | 15 | import { getSort, throwIfNotValid } from '../utils' |
11 | import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' | ||
12 | import { FindAndCountOptions, json } from 'sequelize' | ||
13 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | ||
14 | import * as Bluebird from 'bluebird' | ||
15 | import { MPlugin, MPluginFormattable } from '@server/typings/models' | ||
16 | 16 | ||
17 | @DefaultScope(() => ({ | 17 | @DefaultScope(() => ({ |
18 | attributes: { | 18 | attributes: { |
@@ -112,7 +112,7 @@ export class PluginModel extends Model<PluginModel> { | |||
112 | return PluginModel.findOne(query) | 112 | return PluginModel.findOne(query) |
113 | } | 113 | } |
114 | 114 | ||
115 | static getSetting (pluginName: string, pluginType: PluginType, settingName: string) { | 115 | static getSetting (pluginName: string, pluginType: PluginType, settingName: string, registeredSettings: RegisterServerSettingOptions[]) { |
116 | const query = { | 116 | const query = { |
117 | attributes: [ 'settings' ], | 117 | attributes: [ 'settings' ], |
118 | where: { | 118 | where: { |
@@ -123,12 +123,51 @@ export class PluginModel extends Model<PluginModel> { | |||
123 | 123 | ||
124 | return PluginModel.findOne(query) | 124 | return PluginModel.findOne(query) |
125 | .then(p => { | 125 | .then(p => { |
126 | if (!p || !p.settings) return undefined | 126 | if (!p || !p.settings || p.settings === undefined) { |
127 | const registered = registeredSettings.find(s => s.name === settingName) | ||
128 | if (!registered || registered.default === undefined) return undefined | ||
129 | |||
130 | return registered.default | ||
131 | } | ||
127 | 132 | ||
128 | return p.settings[settingName] | 133 | return p.settings[settingName] |
129 | }) | 134 | }) |
130 | } | 135 | } |
131 | 136 | ||
137 | static getSettings ( | ||
138 | pluginName: string, | ||
139 | pluginType: PluginType, | ||
140 | settingNames: string[], | ||
141 | registeredSettings: RegisterServerSettingOptions[] | ||
142 | ) { | ||
143 | const query = { | ||
144 | attributes: [ 'settings' ], | ||
145 | where: { | ||
146 | name: pluginName, | ||
147 | type: pluginType | ||
148 | } | ||
149 | } | ||
150 | |||
151 | return PluginModel.findOne(query) | ||
152 | .then(p => { | ||
153 | const result: { [settingName: string ]: string | boolean } = {} | ||
154 | |||
155 | for (const name of settingNames) { | ||
156 | if (!p || !p.settings || p.settings[name] === undefined) { | ||
157 | const registered = registeredSettings.find(s => s.name === name) | ||
158 | |||
159 | if (registered?.default !== undefined) { | ||
160 | result[name] = registered.default | ||
161 | } | ||
162 | } else { | ||
163 | result[name] = p.settings[name] | ||
164 | } | ||
165 | } | ||
166 | |||
167 | return result | ||
168 | }) | ||
169 | } | ||
170 | |||
132 | static setSetting (pluginName: string, pluginType: PluginType, settingName: string, settingValue: string) { | 171 | static setSetting (pluginName: string, pluginType: PluginType, settingName: string, settingValue: string) { |
133 | const query = { | 172 | const query = { |
134 | where: { | 173 | where: { |
@@ -173,26 +212,25 @@ export class PluginModel extends Model<PluginModel> { | |||
173 | } | 212 | } |
174 | 213 | ||
175 | static storeData (pluginName: string, pluginType: PluginType, key: string, data: any) { | 214 | static storeData (pluginName: string, pluginType: PluginType, key: string, data: any) { |
176 | const query = { | 215 | const query = 'UPDATE "plugin" SET "storage" = jsonb_set(coalesce("storage", \'{}\'), :key, :data::jsonb) ' + |
177 | where: { | 216 | 'WHERE "name" = :pluginName AND "type" = :pluginType' |
178 | name: pluginName, | ||
179 | type: pluginType | ||
180 | } | ||
181 | } | ||
182 | 217 | ||
183 | const toSave = { | 218 | const jsonPath = '{' + key + '}' |
184 | [`storage.${key}`]: data | 219 | |
220 | const options = { | ||
221 | replacements: { pluginName, pluginType, key: jsonPath, data: JSON.stringify(data) }, | ||
222 | type: QueryTypes.UPDATE | ||
185 | } | 223 | } |
186 | 224 | ||
187 | return PluginModel.update(toSave, query) | 225 | return PluginModel.sequelize.query(query, options) |
188 | .then(() => undefined) | 226 | .then(() => undefined) |
189 | } | 227 | } |
190 | 228 | ||
191 | static listForApi (options: { | 229 | static listForApi (options: { |
192 | pluginType?: PluginType, | 230 | pluginType?: PluginType |
193 | uninstalled?: boolean, | 231 | uninstalled?: boolean |
194 | start: number, | 232 | start: number |
195 | count: number, | 233 | count: number |
196 | sort: string | 234 | sort: string |
197 | }) { | 235 | }) { |
198 | const { uninstalled = false } = options | 236 | const { uninstalled = false } = options |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index b88df4fd5..892024c04 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -2,7 +2,7 @@ import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, Updated | |||
2 | import { AccountModel } from '../account/account' | 2 | import { AccountModel } from '../account/account' |
3 | import { ServerModel } from './server' | 3 | import { ServerModel } from './server' |
4 | import { ServerBlock } from '../../../shared/models/blocklist' | 4 | import { ServerBlock } from '../../../shared/models/blocklist' |
5 | import { getSort } from '../utils' | 5 | import { getSort, searchAttribute } from '../utils' |
6 | import * as Bluebird from 'bluebird' | 6 | import * as Bluebird from 'bluebird' |
7 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models' | 7 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models' |
8 | import { Op } from 'sequelize' | 8 | import { Op } from 'sequelize' |
@@ -81,7 +81,7 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> { | |||
81 | attributes: [ 'accountId', 'id' ], | 81 | attributes: [ 'accountId', 'id' ], |
82 | where: { | 82 | where: { |
83 | accountId: { | 83 | accountId: { |
84 | [Op.in]: accountIds // FIXME: sequelize ANY seems broken | 84 | [Op.in]: accountIds |
85 | }, | 85 | }, |
86 | targetServerId | 86 | targetServerId |
87 | }, | 87 | }, |
@@ -120,13 +120,22 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> { | |||
120 | return ServerBlocklistModel.findOne(query) | 120 | return ServerBlocklistModel.findOne(query) |
121 | } | 121 | } |
122 | 122 | ||
123 | static listForApi (accountId: number, start: number, count: number, sort: string) { | 123 | static listForApi (parameters: { |
124 | start: number | ||
125 | count: number | ||
126 | sort: string | ||
127 | search?: string | ||
128 | accountId: number | ||
129 | }) { | ||
130 | const { start, count, sort, search, accountId } = parameters | ||
131 | |||
124 | const query = { | 132 | const query = { |
125 | offset: start, | 133 | offset: start, |
126 | limit: count, | 134 | limit: count, |
127 | order: getSort(sort), | 135 | order: getSort(sort), |
128 | where: { | 136 | where: { |
129 | accountId | 137 | accountId, |
138 | ...searchAttribute(search, '$BlockedServer.host$') | ||
130 | } | 139 | } |
131 | } | 140 | } |
132 | 141 | ||
diff --git a/server/models/server/server.ts b/server/models/server/server.ts index 8b07115f1..5131257ec 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts | |||
@@ -71,6 +71,13 @@ export class ServerModel extends Model<ServerModel> { | |||
71 | return ServerModel.findOne(query) | 71 | return ServerModel.findOne(query) |
72 | } | 72 | } |
73 | 73 | ||
74 | static async loadOrCreateByHost (host: string) { | ||
75 | let server = await ServerModel.loadByHost(host) | ||
76 | if (!server) server = await ServerModel.create({ host }) | ||
77 | |||
78 | return server | ||
79 | } | ||
80 | |||
74 | isBlocked () { | 81 | isBlocked () { |
75 | return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 | 82 | return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 |
76 | } | 83 | } |
diff --git a/server/models/utils.ts b/server/models/utils.ts index f89b80011..b2573cd35 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -1,7 +1,24 @@ | |||
1 | import { Model, Sequelize } from 'sequelize-typescript' | 1 | import { Model, Sequelize } from 'sequelize-typescript' |
2 | import validator from 'validator' | 2 | import validator from 'validator' |
3 | import { Col } from 'sequelize/types/lib/utils' | 3 | import { Col } from 'sequelize/types/lib/utils' |
4 | import { literal, OrderItem } from 'sequelize' | 4 | import { literal, OrderItem, Op } from 'sequelize' |
5 | |||
6 | type Primitive = string | Function | number | boolean | Symbol | undefined | null | ||
7 | type DeepOmitHelper<T, K extends keyof T> = { | ||
8 | [P in K]: // extra level of indirection needed to trigger homomorhic behavior | ||
9 | T[P] extends infer TP // distribute over unions | ||
10 | ? TP extends Primitive | ||
11 | ? TP // leave primitives and functions alone | ||
12 | : TP extends any[] | ||
13 | ? DeepOmitArray<TP, K> // Array special handling | ||
14 | : DeepOmit<TP, K> | ||
15 | : never | ||
16 | } | ||
17 | type DeepOmit<T, K> = T extends Primitive ? T : DeepOmitHelper<T, Exclude<keyof T, K>> | ||
18 | |||
19 | type DeepOmitArray<T extends any[], K> = { | ||
20 | [P in keyof T]: DeepOmit<T[P], K> | ||
21 | } | ||
5 | 22 | ||
6 | type SortType = { sortModel: string, sortValue: string } | 23 | type SortType = { sortModel: string, sortValue: string } |
7 | 24 | ||
@@ -67,7 +84,7 @@ function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): Or | |||
67 | function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { | 84 | function getBlacklistSort (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
68 | const [ firstSort ] = getSort(value) | 85 | const [ firstSort ] = getSort(value) |
69 | 86 | ||
70 | if (model) return [ [ literal(`"${model}.${firstSort[ 0 ]}" ${firstSort[ 1 ]}`) ], lastSort ] as any[] // FIXME: typings | 87 | if (model) return [ [ literal(`"${model}.${firstSort[0]}" ${firstSort[1]}`) ], lastSort ] as any[] // FIXME: typings |
71 | return [ firstSort, lastSort ] | 88 | return [ firstSort, lastSort ] |
72 | } | 89 | } |
73 | 90 | ||
@@ -139,7 +156,7 @@ function buildServerIdsFollowedBy (actorId: any) { | |||
139 | 'SELECT "actor"."serverId" FROM "actorFollow" ' + | 156 | 'SELECT "actor"."serverId" FROM "actorFollow" ' + |
140 | 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' + | 157 | 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' + |
141 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | 158 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + |
142 | ')' | 159 | ')' |
143 | } | 160 | } |
144 | 161 | ||
145 | function buildWhereIdOrUUID (id: number | string) { | 162 | function buildWhereIdOrUUID (id: number | string) { |
@@ -156,8 +173,11 @@ function parseAggregateResult (result: any) { | |||
156 | } | 173 | } |
157 | 174 | ||
158 | const createSafeIn = (model: typeof Model, stringArr: (string | number)[]) => { | 175 | const createSafeIn = (model: typeof Model, stringArr: (string | number)[]) => { |
159 | return stringArr.map(t => model.sequelize.escape('' + t)) | 176 | return stringArr.map(t => { |
160 | .join(', ') | 177 | return t === null |
178 | ? null | ||
179 | : model.sequelize.escape('' + t) | ||
180 | }).join(', ') | ||
161 | } | 181 | } |
162 | 182 | ||
163 | function buildLocalAccountIdsIn () { | 183 | function buildLocalAccountIdsIn () { |
@@ -172,9 +192,35 @@ function buildLocalActorIdsIn () { | |||
172 | ) | 192 | ) |
173 | } | 193 | } |
174 | 194 | ||
195 | function buildDirectionAndField (value: string) { | ||
196 | let field: string | ||
197 | let direction: 'ASC' | 'DESC' | ||
198 | |||
199 | if (value.substring(0, 1) === '-') { | ||
200 | direction = 'DESC' | ||
201 | field = value.substring(1) | ||
202 | } else { | ||
203 | direction = 'ASC' | ||
204 | field = value | ||
205 | } | ||
206 | |||
207 | return { direction, field } | ||
208 | } | ||
209 | |||
210 | function searchAttribute (sourceField?: string, targetField?: string) { | ||
211 | if (!sourceField) return {} | ||
212 | |||
213 | return { | ||
214 | [targetField]: { | ||
215 | [Op.iLike]: `%${sourceField}%` | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | |||
175 | // --------------------------------------------------------------------------- | 220 | // --------------------------------------------------------------------------- |
176 | 221 | ||
177 | export { | 222 | export { |
223 | DeepOmit, | ||
178 | buildBlockedAccountSQL, | 224 | buildBlockedAccountSQL, |
179 | buildLocalActorIdsIn, | 225 | buildLocalActorIdsIn, |
180 | SortType, | 226 | SortType, |
@@ -191,7 +237,9 @@ export { | |||
191 | isOutdated, | 237 | isOutdated, |
192 | parseAggregateResult, | 238 | parseAggregateResult, |
193 | getFollowsSort, | 239 | getFollowsSort, |
194 | createSafeIn | 240 | buildDirectionAndField, |
241 | createSafeIn, | ||
242 | searchAttribute | ||
195 | } | 243 | } |
196 | 244 | ||
197 | // --------------------------------------------------------------------------- | 245 | // --------------------------------------------------------------------------- |
@@ -203,18 +251,3 @@ function searchTrigramNormalizeValue (value: string) { | |||
203 | function searchTrigramNormalizeCol (col: string) { | 251 | function searchTrigramNormalizeCol (col: string) { |
204 | return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col))) | 252 | return Sequelize.fn('lower', Sequelize.fn('immutable_unaccent', Sequelize.col(col))) |
205 | } | 253 | } |
206 | |||
207 | function buildDirectionAndField (value: string) { | ||
208 | let field: string | ||
209 | let direction: 'ASC' | 'DESC' | ||
210 | |||
211 | if (value.substring(0, 1) === '-') { | ||
212 | direction = 'DESC' | ||
213 | field = value.substring(1) | ||
214 | } else { | ||
215 | direction = 'ASC' | ||
216 | field = value | ||
217 | } | ||
218 | |||
219 | return { direction, field } | ||
220 | } | ||
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts index 3b011b1d2..e396784d2 100644 --- a/server/models/video/thumbnail.ts +++ b/server/models/video/thumbnail.ts | |||
@@ -19,6 +19,8 @@ import { CONFIG } from '../../initializers/config' | |||
19 | import { VideoModel } from './video' | 19 | import { VideoModel } from './video' |
20 | import { VideoPlaylistModel } from './video-playlist' | 20 | import { VideoPlaylistModel } from './video-playlist' |
21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 21 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
22 | import { MVideoAccountLight } from '@server/typings/models' | ||
23 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | ||
22 | 24 | ||
23 | @Table({ | 25 | @Table({ |
24 | tableName: 'thumbnail', | 26 | tableName: 'thumbnail', |
@@ -90,7 +92,7 @@ export class ThumbnailModel extends Model<ThumbnailModel> { | |||
90 | @UpdatedAt | 92 | @UpdatedAt |
91 | updatedAt: Date | 93 | updatedAt: Date |
92 | 94 | ||
93 | private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { | 95 | private static readonly types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { |
94 | [ThumbnailType.MINIATURE]: { | 96 | [ThumbnailType.MINIATURE]: { |
95 | label: 'miniature', | 97 | label: 'miniature', |
96 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, | 98 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, |
@@ -126,11 +128,14 @@ export class ThumbnailModel extends Model<ThumbnailModel> { | |||
126 | return videoUUID + '.jpg' | 128 | return videoUUID + '.jpg' |
127 | } | 129 | } |
128 | 130 | ||
129 | getFileUrl (isLocal: boolean) { | 131 | getFileUrl (video: MVideoAccountLight) { |
130 | if (isLocal === false) return this.fileUrl | 132 | const staticPath = ThumbnailModel.types[this.type].staticPath + this.filename |
131 | 133 | ||
132 | const staticPath = ThumbnailModel.types[this.type].staticPath | 134 | if (video.isOwned()) return WEBSERVER.URL + staticPath |
133 | return WEBSERVER.URL + staticPath + this.filename | 135 | if (this.fileUrl) return this.fileUrl |
136 | |||
137 | // Fallback if we don't have a file URL | ||
138 | return buildRemoteVideoBaseUrl(video, staticPath) | ||
134 | } | 139 | } |
135 | 140 | ||
136 | getPath () { | 141 | getPath () { |
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 3636db18d..0844f702d 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -1,4 +1,21 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import * as Bluebird from 'bluebird' |
2 | import { literal, Op } from 'sequelize' | ||
3 | import { | ||
4 | AllowNull, | ||
5 | BelongsTo, | ||
6 | Column, | ||
7 | CreatedAt, | ||
8 | DataType, | ||
9 | Default, | ||
10 | ForeignKey, | ||
11 | Is, | ||
12 | Model, | ||
13 | Scopes, | ||
14 | Table, | ||
15 | UpdatedAt | ||
16 | } from 'sequelize-typescript' | ||
17 | import { VideoAbuseVideoIs } from '@shared/models/videos/abuse/video-abuse-video-is.type' | ||
18 | import { VideoAbuseState, VideoDetails } from '../../../shared' | ||
2 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' | 19 | import { VideoAbuseObject } from '../../../shared/models/activitypub/objects' |
3 | import { VideoAbuse } from '../../../shared/models/videos' | 20 | import { VideoAbuse } from '../../../shared/models/videos' |
4 | import { | 21 | import { |
@@ -6,15 +23,205 @@ import { | |||
6 | isVideoAbuseReasonValid, | 23 | isVideoAbuseReasonValid, |
7 | isVideoAbuseStateValid | 24 | isVideoAbuseStateValid |
8 | } from '../../helpers/custom-validators/video-abuses' | 25 | } from '../../helpers/custom-validators/video-abuses' |
9 | import { AccountModel } from '../account/account' | ||
10 | import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' | ||
11 | import { VideoModel } from './video' | ||
12 | import { VideoAbuseState } from '../../../shared' | ||
13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | 26 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' |
14 | import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models' | 27 | import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models' |
15 | import * as Bluebird from 'bluebird' | 28 | import { AccountModel } from '../account/account' |
16 | import { literal, Op } from 'sequelize' | 29 | import { buildBlockedAccountSQL, getSort, searchAttribute, throwIfNotValid } from '../utils' |
30 | import { ThumbnailModel } from './thumbnail' | ||
31 | import { VideoModel } from './video' | ||
32 | import { VideoBlacklistModel } from './video-blacklist' | ||
33 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | ||
34 | |||
35 | export enum ScopeNames { | ||
36 | FOR_API = 'FOR_API' | ||
37 | } | ||
38 | |||
39 | @Scopes(() => ({ | ||
40 | [ScopeNames.FOR_API]: (options: { | ||
41 | // search | ||
42 | search?: string | ||
43 | searchReporter?: string | ||
44 | searchReportee?: string | ||
45 | searchVideo?: string | ||
46 | searchVideoChannel?: string | ||
47 | |||
48 | // filters | ||
49 | id?: number | ||
50 | |||
51 | state?: VideoAbuseState | ||
52 | videoIs?: VideoAbuseVideoIs | ||
53 | |||
54 | // accountIds | ||
55 | serverAccountId: number | ||
56 | userAccountId: number | ||
57 | }) => { | ||
58 | const where = { | ||
59 | reporterAccountId: { | ||
60 | [Op.notIn]: literal('(' + buildBlockedAccountSQL(options.serverAccountId, options.userAccountId) + ')') | ||
61 | } | ||
62 | } | ||
63 | |||
64 | if (options.search) { | ||
65 | Object.assign(where, { | ||
66 | [Op.or]: [ | ||
67 | { | ||
68 | [Op.and]: [ | ||
69 | { videoId: { [Op.not]: null } }, | ||
70 | searchAttribute(options.search, '$Video.name$') | ||
71 | ] | ||
72 | }, | ||
73 | { | ||
74 | [Op.and]: [ | ||
75 | { videoId: { [Op.not]: null } }, | ||
76 | searchAttribute(options.search, '$Video.VideoChannel.name$') | ||
77 | ] | ||
78 | }, | ||
79 | { | ||
80 | [Op.and]: [ | ||
81 | { deletedVideo: { [Op.not]: null } }, | ||
82 | { deletedVideo: searchAttribute(options.search, 'name') } | ||
83 | ] | ||
84 | }, | ||
85 | { | ||
86 | [Op.and]: [ | ||
87 | { deletedVideo: { [Op.not]: null } }, | ||
88 | { deletedVideo: { channel: searchAttribute(options.search, 'displayName') } } | ||
89 | ] | ||
90 | }, | ||
91 | searchAttribute(options.search, '$Account.name$') | ||
92 | ] | ||
93 | }) | ||
94 | } | ||
17 | 95 | ||
96 | if (options.id) Object.assign(where, { id: options.id }) | ||
97 | if (options.state) Object.assign(where, { state: options.state }) | ||
98 | |||
99 | if (options.videoIs === 'deleted') { | ||
100 | Object.assign(where, { | ||
101 | deletedVideo: { | ||
102 | [Op.not]: null | ||
103 | } | ||
104 | }) | ||
105 | } | ||
106 | |||
107 | const onlyBlacklisted = options.videoIs === 'blacklisted' | ||
108 | |||
109 | return { | ||
110 | attributes: { | ||
111 | include: [ | ||
112 | [ | ||
113 | // we don't care about this count for deleted videos, so there are not included | ||
114 | literal( | ||
115 | '(' + | ||
116 | 'SELECT count(*) ' + | ||
117 | 'FROM "videoAbuse" ' + | ||
118 | 'WHERE "videoId" = "VideoAbuseModel"."videoId" ' + | ||
119 | ')' | ||
120 | ), | ||
121 | 'countReportsForVideo' | ||
122 | ], | ||
123 | [ | ||
124 | // we don't care about this count for deleted videos, so there are not included | ||
125 | literal( | ||
126 | '(' + | ||
127 | 'SELECT t.nth ' + | ||
128 | 'FROM ( ' + | ||
129 | 'SELECT id, ' + | ||
130 | 'row_number() OVER (PARTITION BY "videoId" ORDER BY "createdAt") AS nth ' + | ||
131 | 'FROM "videoAbuse" ' + | ||
132 | ') t ' + | ||
133 | 'WHERE t.id = "VideoAbuseModel".id ' + | ||
134 | ')' | ||
135 | ), | ||
136 | 'nthReportForVideo' | ||
137 | ], | ||
138 | [ | ||
139 | literal( | ||
140 | '(' + | ||
141 | 'SELECT count("videoAbuse"."id") ' + | ||
142 | 'FROM "videoAbuse" ' + | ||
143 | 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' + | ||
144 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
145 | 'INNER JOIN "account" ON "videoChannel"."accountId" = "account"."id" ' + | ||
146 | 'WHERE "account"."id" = "VideoAbuseModel"."reporterAccountId" ' + | ||
147 | ')' | ||
148 | ), | ||
149 | 'countReportsForReporter__video' | ||
150 | ], | ||
151 | [ | ||
152 | literal( | ||
153 | '(' + | ||
154 | 'SELECT count(DISTINCT "videoAbuse"."id") ' + | ||
155 | 'FROM "videoAbuse" ' + | ||
156 | `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "VideoAbuseModel"."reporterAccountId" ` + | ||
157 | ')' | ||
158 | ), | ||
159 | 'countReportsForReporter__deletedVideo' | ||
160 | ], | ||
161 | [ | ||
162 | literal( | ||
163 | '(' + | ||
164 | 'SELECT count(DISTINCT "videoAbuse"."id") ' + | ||
165 | 'FROM "videoAbuse" ' + | ||
166 | 'INNER JOIN "video" ON "video"."id" = "videoAbuse"."videoId" ' + | ||
167 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
168 | 'INNER JOIN "account" ON ' + | ||
169 | '"videoChannel"."accountId" = "Video->VideoChannel"."accountId" ' + | ||
170 | `OR "videoChannel"."accountId" = CAST("VideoAbuseModel"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` + | ||
171 | ')' | ||
172 | ), | ||
173 | 'countReportsForReportee__video' | ||
174 | ], | ||
175 | [ | ||
176 | literal( | ||
177 | '(' + | ||
178 | 'SELECT count(DISTINCT "videoAbuse"."id") ' + | ||
179 | 'FROM "videoAbuse" ' + | ||
180 | `WHERE CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = "Video->VideoChannel"."accountId" ` + | ||
181 | `OR CAST("deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) = ` + | ||
182 | `CAST("VideoAbuseModel"."deletedVideo"->'channel'->'ownerAccount'->>'id' AS INTEGER) ` + | ||
183 | ')' | ||
184 | ), | ||
185 | 'countReportsForReportee__deletedVideo' | ||
186 | ] | ||
187 | ] | ||
188 | }, | ||
189 | include: [ | ||
190 | { | ||
191 | model: AccountModel, | ||
192 | required: true, | ||
193 | where: searchAttribute(options.searchReporter, 'name') | ||
194 | }, | ||
195 | { | ||
196 | model: VideoModel, | ||
197 | required: !!(onlyBlacklisted || options.searchVideo || options.searchReportee || options.searchVideoChannel), | ||
198 | where: searchAttribute(options.searchVideo, 'name'), | ||
199 | include: [ | ||
200 | { | ||
201 | model: ThumbnailModel | ||
202 | }, | ||
203 | { | ||
204 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), | ||
205 | where: searchAttribute(options.searchVideoChannel, 'name'), | ||
206 | include: [ | ||
207 | { | ||
208 | model: AccountModel, | ||
209 | where: searchAttribute(options.searchReportee, 'name') | ||
210 | } | ||
211 | ] | ||
212 | }, | ||
213 | { | ||
214 | attributes: [ 'id', 'reason', 'unfederated' ], | ||
215 | model: VideoBlacklistModel, | ||
216 | required: onlyBlacklisted | ||
217 | } | ||
218 | ] | ||
219 | } | ||
220 | ], | ||
221 | where | ||
222 | } | ||
223 | } | ||
224 | })) | ||
18 | @Table({ | 225 | @Table({ |
19 | tableName: 'videoAbuse', | 226 | tableName: 'videoAbuse', |
20 | indexes: [ | 227 | indexes: [ |
@@ -46,6 +253,11 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
46 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) | 253 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) |
47 | moderationComment: string | 254 | moderationComment: string |
48 | 255 | ||
256 | @AllowNull(true) | ||
257 | @Default(null) | ||
258 | @Column(DataType.JSONB) | ||
259 | deletedVideo: VideoDetails | ||
260 | |||
49 | @CreatedAt | 261 | @CreatedAt |
50 | createdAt: Date | 262 | createdAt: Date |
51 | 263 | ||
@@ -58,9 +270,9 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
58 | 270 | ||
59 | @BelongsTo(() => AccountModel, { | 271 | @BelongsTo(() => AccountModel, { |
60 | foreignKey: { | 272 | foreignKey: { |
61 | allowNull: false | 273 | allowNull: true |
62 | }, | 274 | }, |
63 | onDelete: 'cascade' | 275 | onDelete: 'set null' |
64 | }) | 276 | }) |
65 | Account: AccountModel | 277 | Account: AccountModel |
66 | 278 | ||
@@ -70,60 +282,103 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
70 | 282 | ||
71 | @BelongsTo(() => VideoModel, { | 283 | @BelongsTo(() => VideoModel, { |
72 | foreignKey: { | 284 | foreignKey: { |
73 | allowNull: false | 285 | allowNull: true |
74 | }, | 286 | }, |
75 | onDelete: 'cascade' | 287 | onDelete: 'set null' |
76 | }) | 288 | }) |
77 | Video: VideoModel | 289 | Video: VideoModel |
78 | 290 | ||
79 | static loadByIdAndVideoId (id: number, videoId: number): Bluebird<MVideoAbuse> { | 291 | static loadByIdAndVideoId (id: number, videoId?: number, uuid?: string): Bluebird<MVideoAbuse> { |
292 | const videoAttributes = {} | ||
293 | if (videoId) videoAttributes['videoId'] = videoId | ||
294 | if (uuid) videoAttributes['deletedVideo'] = { uuid } | ||
295 | |||
80 | const query = { | 296 | const query = { |
81 | where: { | 297 | where: { |
82 | id, | 298 | id, |
83 | videoId | 299 | ...videoAttributes |
84 | } | 300 | } |
85 | } | 301 | } |
86 | return VideoAbuseModel.findOne(query) | 302 | return VideoAbuseModel.findOne(query) |
87 | } | 303 | } |
88 | 304 | ||
89 | static listForApi (parameters: { | 305 | static listForApi (parameters: { |
90 | start: number, | 306 | start: number |
91 | count: number, | 307 | count: number |
92 | sort: string, | 308 | sort: string |
309 | |||
93 | serverAccountId: number | 310 | serverAccountId: number |
94 | user?: MUserAccountId | 311 | user?: MUserAccountId |
312 | |||
313 | id?: number | ||
314 | state?: VideoAbuseState | ||
315 | videoIs?: VideoAbuseVideoIs | ||
316 | |||
317 | search?: string | ||
318 | searchReporter?: string | ||
319 | searchReportee?: string | ||
320 | searchVideo?: string | ||
321 | searchVideoChannel?: string | ||
95 | }) { | 322 | }) { |
96 | const { start, count, sort, user, serverAccountId } = parameters | 323 | const { |
324 | start, | ||
325 | count, | ||
326 | sort, | ||
327 | search, | ||
328 | user, | ||
329 | serverAccountId, | ||
330 | state, | ||
331 | videoIs, | ||
332 | searchReportee, | ||
333 | searchVideo, | ||
334 | searchVideoChannel, | ||
335 | searchReporter, | ||
336 | id | ||
337 | } = parameters | ||
338 | |||
97 | const userAccountId = user ? user.Account.id : undefined | 339 | const userAccountId = user ? user.Account.id : undefined |
98 | 340 | ||
99 | const query = { | 341 | const query = { |
100 | offset: start, | 342 | offset: start, |
101 | limit: count, | 343 | limit: count, |
102 | order: getSort(sort), | 344 | order: getSort(sort), |
103 | where: { | 345 | col: 'VideoAbuseModel.id', |
104 | reporterAccountId: { | 346 | distinct: true |
105 | [Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')') | ||
106 | } | ||
107 | }, | ||
108 | include: [ | ||
109 | { | ||
110 | model: AccountModel, | ||
111 | required: true | ||
112 | }, | ||
113 | { | ||
114 | model: VideoModel, | ||
115 | required: true | ||
116 | } | ||
117 | ] | ||
118 | } | 347 | } |
119 | 348 | ||
120 | return VideoAbuseModel.findAndCountAll(query) | 349 | const filters = { |
350 | id, | ||
351 | search, | ||
352 | state, | ||
353 | videoIs, | ||
354 | searchReportee, | ||
355 | searchVideo, | ||
356 | searchVideoChannel, | ||
357 | searchReporter, | ||
358 | serverAccountId, | ||
359 | userAccountId | ||
360 | } | ||
361 | |||
362 | return VideoAbuseModel | ||
363 | .scope({ method: [ ScopeNames.FOR_API, filters ] }) | ||
364 | .findAndCountAll(query) | ||
121 | .then(({ rows, count }) => { | 365 | .then(({ rows, count }) => { |
122 | return { total: count, data: rows } | 366 | return { total: count, data: rows } |
123 | }) | 367 | }) |
124 | } | 368 | } |
125 | 369 | ||
126 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { | 370 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { |
371 | const countReportsForVideo = this.get('countReportsForVideo') as number | ||
372 | const nthReportForVideo = this.get('nthReportForVideo') as number | ||
373 | const countReportsForReporterVideo = this.get('countReportsForReporter__video') as number | ||
374 | const countReportsForReporterDeletedVideo = this.get('countReportsForReporter__deletedVideo') as number | ||
375 | const countReportsForReporteeVideo = this.get('countReportsForReportee__video') as number | ||
376 | const countReportsForReporteeDeletedVideo = this.get('countReportsForReportee__deletedVideo') as number | ||
377 | |||
378 | const video = this.Video | ||
379 | ? this.Video | ||
380 | : this.deletedVideo | ||
381 | |||
127 | return { | 382 | return { |
128 | id: this.id, | 383 | id: this.id, |
129 | reason: this.reason, | 384 | reason: this.reason, |
@@ -134,11 +389,21 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
134 | }, | 389 | }, |
135 | moderationComment: this.moderationComment, | 390 | moderationComment: this.moderationComment, |
136 | video: { | 391 | video: { |
137 | id: this.Video.id, | 392 | id: video.id, |
138 | uuid: this.Video.uuid, | 393 | uuid: video.uuid, |
139 | name: this.Video.name | 394 | name: video.name, |
395 | nsfw: video.nsfw, | ||
396 | deleted: !this.Video, | ||
397 | blacklisted: this.Video && this.Video.isBlacklisted(), | ||
398 | thumbnailPath: this.Video?.getMiniatureStaticPath(), | ||
399 | channel: this.Video?.VideoChannel.toFormattedJSON() || this.deletedVideo?.channel | ||
140 | }, | 400 | }, |
141 | createdAt: this.createdAt | 401 | createdAt: this.createdAt, |
402 | updatedAt: this.updatedAt, | ||
403 | count: countReportsForVideo || 0, | ||
404 | nth: nthReportForVideo || 0, | ||
405 | countReportsForReporter: (countReportsForReporterVideo || 0) + (countReportsForReporterDeletedVideo || 0), | ||
406 | countReportsForReportee: (countReportsForReporteeVideo || 0) + (countReportsForReporteeDeletedVideo || 0) | ||
142 | } | 407 | } |
143 | } | 408 | } |
144 | 409 | ||
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index 694983cb3..8cbfe362e 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts | |||
@@ -1,5 +1,5 @@ | |||
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 { getBlacklistSort, SortType, throwIfNotValid } from '../utils' | 2 | import { getBlacklistSort, SortType, throwIfNotValid, searchAttribute } from '../utils' |
3 | import { VideoModel } from './video' | 3 | import { VideoModel } from './video' |
4 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, 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' |
@@ -54,7 +54,15 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
54 | }) | 54 | }) |
55 | Video: VideoModel | 55 | Video: VideoModel |
56 | 56 | ||
57 | static listForApi (start: number, count: number, sort: SortType, type?: VideoBlacklistType) { | 57 | static listForApi (parameters: { |
58 | start: number | ||
59 | count: number | ||
60 | sort: SortType | ||
61 | search?: string | ||
62 | type?: VideoBlacklistType | ||
63 | }) { | ||
64 | const { start, count, sort, search, type } = parameters | ||
65 | |||
58 | function buildBaseQuery (): FindOptions { | 66 | function buildBaseQuery (): FindOptions { |
59 | return { | 67 | return { |
60 | offset: start, | 68 | offset: start, |
@@ -70,6 +78,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
70 | { | 78 | { |
71 | model: VideoModel, | 79 | model: VideoModel, |
72 | required: true, | 80 | required: true, |
81 | where: searchAttribute(search, 'name'), | ||
73 | include: [ | 82 | include: [ |
74 | { | 83 | { |
75 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), | 84 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, { withAccount: true } as SummaryOptions ] }), |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index eeb2a4afd..59d3e1050 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -5,6 +5,7 @@ import { | |||
5 | BelongsTo, | 5 | BelongsTo, |
6 | Column, | 6 | Column, |
7 | CreatedAt, | 7 | CreatedAt, |
8 | DataType, | ||
8 | ForeignKey, | 9 | ForeignKey, |
9 | Is, | 10 | Is, |
10 | Model, | 11 | Model, |
@@ -16,13 +17,14 @@ import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' | |||
16 | import { VideoModel } from './video' | 17 | import { VideoModel } from './video' |
17 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' | 18 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' |
18 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' | 19 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' |
19 | import { LAZY_STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers/constants' | 20 | import { CONSTRAINTS_FIELDS, LAZY_STATIC_PATHS, VIDEO_LANGUAGES, WEBSERVER } from '../../initializers/constants' |
20 | import { join } from 'path' | 21 | import { join } from 'path' |
21 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
22 | import { remove } from 'fs-extra' | 23 | import { remove } from 'fs-extra' |
23 | import { CONFIG } from '../../initializers/config' | 24 | import { CONFIG } from '../../initializers/config' |
24 | import * as Bluebird from 'bluebird' | 25 | import * as Bluebird from 'bluebird' |
25 | import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' | 26 | import { MVideoAccountLight, MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' |
27 | import { buildRemoteVideoBaseUrl } from '@server/helpers/activitypub' | ||
26 | 28 | ||
27 | export enum ScopeNames { | 29 | export enum ScopeNames { |
28 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 30 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
@@ -64,6 +66,10 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
64 | @Column | 66 | @Column |
65 | language: string | 67 | language: string |
66 | 68 | ||
69 | @AllowNull(true) | ||
70 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max)) | ||
71 | fileUrl: string | ||
72 | |||
67 | @ForeignKey(() => VideoModel) | 73 | @ForeignKey(() => VideoModel) |
68 | @Column | 74 | @Column |
69 | videoId: number | 75 | videoId: number |
@@ -114,13 +120,14 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
114 | return VideoCaptionModel.findOne(query) | 120 | return VideoCaptionModel.findOne(query) |
115 | } | 121 | } |
116 | 122 | ||
117 | static insertOrReplaceLanguage (videoId: number, language: string, transaction: Transaction) { | 123 | static insertOrReplaceLanguage (videoId: number, language: string, fileUrl: string, transaction: Transaction) { |
118 | const values = { | 124 | const values = { |
119 | videoId, | 125 | videoId, |
120 | language | 126 | language, |
127 | fileUrl | ||
121 | } | 128 | } |
122 | 129 | ||
123 | return (VideoCaptionModel.upsert<VideoCaptionModel>(values, { transaction, returning: true }) as any) // FIXME: typings | 130 | return VideoCaptionModel.upsert(values, { transaction, returning: true }) |
124 | .then(([ caption ]) => caption) | 131 | .then(([ caption ]) => caption) |
125 | } | 132 | } |
126 | 133 | ||
@@ -175,4 +182,14 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
175 | removeCaptionFile (this: MVideoCaptionFormattable) { | 182 | removeCaptionFile (this: MVideoCaptionFormattable) { |
176 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) | 183 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) |
177 | } | 184 | } |
185 | |||
186 | getFileUrl (video: MVideoAccountLight) { | ||
187 | if (!this.Video) this.Video = video as VideoModel | ||
188 | |||
189 | if (video.isOwned()) return WEBSERVER.URL + this.getCaptionStaticPath() | ||
190 | if (this.fileUrl) return this.fileUrl | ||
191 | |||
192 | // Fallback if we don't have a file URL | ||
193 | return buildRemoteVideoBaseUrl(video, this.getCaptionStaticPath()) | ||
194 | } | ||
178 | } | 195 | } |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index e10adcb3a..642e129ff 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -30,7 +30,7 @@ import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttr | |||
30 | import { VideoModel } from './video' | 30 | import { VideoModel } from './video' |
31 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | 31 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
32 | import { ServerModel } from '../server/server' | 32 | import { ServerModel } from '../server/server' |
33 | import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' | 33 | import { FindOptions, Op, literal, ScopeOptions } from 'sequelize' |
34 | import { AvatarModel } from '../avatar/avatar' | 34 | import { AvatarModel } from '../avatar/avatar' |
35 | import { VideoPlaylistModel } from './video-playlist' | 35 | import { VideoPlaylistModel } from './video-playlist' |
36 | import * as Bluebird from 'bluebird' | 36 | import * as Bluebird from 'bluebird' |
@@ -43,30 +43,23 @@ import { | |||
43 | MChannelSummaryFormattable | 43 | MChannelSummaryFormattable |
44 | } from '../../typings/models/video' | 44 | } from '../../typings/models/video' |
45 | 45 | ||
46 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | ||
47 | const indexes: ModelIndexesOptions[] = [ | ||
48 | buildTrigramSearchIndex('video_channel_name_trigram', 'name'), | ||
49 | |||
50 | { | ||
51 | fields: [ 'accountId' ] | ||
52 | }, | ||
53 | { | ||
54 | fields: [ 'actorId' ] | ||
55 | } | ||
56 | ] | ||
57 | |||
58 | export enum ScopeNames { | 46 | export enum ScopeNames { |
59 | FOR_API = 'FOR_API', | 47 | FOR_API = 'FOR_API', |
48 | SUMMARY = 'SUMMARY', | ||
60 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 49 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
61 | WITH_ACTOR = 'WITH_ACTOR', | 50 | WITH_ACTOR = 'WITH_ACTOR', |
62 | WITH_VIDEOS = 'WITH_VIDEOS', | 51 | WITH_VIDEOS = 'WITH_VIDEOS', |
63 | SUMMARY = 'SUMMARY' | 52 | WITH_STATS = 'WITH_STATS' |
64 | } | 53 | } |
65 | 54 | ||
66 | type AvailableForListOptions = { | 55 | type AvailableForListOptions = { |
67 | actorId: number | 56 | actorId: number |
68 | } | 57 | } |
69 | 58 | ||
59 | type AvailableWithStatsOptions = { | ||
60 | daysPrior: number | ||
61 | } | ||
62 | |||
70 | export type SummaryOptions = { | 63 | export type SummaryOptions = { |
71 | withAccount?: boolean // Default: false | 64 | withAccount?: boolean // Default: false |
72 | withAccountBlockerIds?: number[] | 65 | withAccountBlockerIds?: number[] |
@@ -81,40 +74,6 @@ export type SummaryOptions = { | |||
81 | ] | 74 | ] |
82 | })) | 75 | })) |
83 | @Scopes(() => ({ | 76 | @Scopes(() => ({ |
84 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { | ||
85 | const base: FindOptions = { | ||
86 | attributes: [ 'id', 'name', 'description', 'actorId' ], | ||
87 | include: [ | ||
88 | { | ||
89 | attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ], | ||
90 | model: ActorModel.unscoped(), | ||
91 | required: true, | ||
92 | include: [ | ||
93 | { | ||
94 | attributes: [ 'host' ], | ||
95 | model: ServerModel.unscoped(), | ||
96 | required: false | ||
97 | }, | ||
98 | { | ||
99 | model: AvatarModel.unscoped(), | ||
100 | required: false | ||
101 | } | ||
102 | ] | ||
103 | } | ||
104 | ] | ||
105 | } | ||
106 | |||
107 | if (options.withAccount === true) { | ||
108 | base.include.push({ | ||
109 | model: AccountModel.scope({ | ||
110 | method: [ AccountModelScopeNames.SUMMARY, { withAccountBlockerIds: options.withAccountBlockerIds } as AccountSummaryOptions ] | ||
111 | }), | ||
112 | required: true | ||
113 | }) | ||
114 | } | ||
115 | |||
116 | return base | ||
117 | }, | ||
118 | [ScopeNames.FOR_API]: (options: AvailableForListOptions) => { | 77 | [ScopeNames.FOR_API]: (options: AvailableForListOptions) => { |
119 | // Only list local channels OR channels that are on an instance followed by actorId | 78 | // Only list local channels OR channels that are on an instance followed by actorId |
120 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) | 79 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) |
@@ -133,7 +92,7 @@ export type SummaryOptions = { | |||
133 | }, | 92 | }, |
134 | { | 93 | { |
135 | serverId: { | 94 | serverId: { |
136 | [ Op.in ]: Sequelize.literal(inQueryInstanceFollow) | 95 | [Op.in]: Sequelize.literal(inQueryInstanceFollow) |
137 | } | 96 | } |
138 | } | 97 | } |
139 | ] | 98 | ] |
@@ -155,6 +114,40 @@ export type SummaryOptions = { | |||
155 | ] | 114 | ] |
156 | } | 115 | } |
157 | }, | 116 | }, |
117 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { | ||
118 | const base: FindOptions = { | ||
119 | attributes: [ 'id', 'name', 'description', 'actorId' ], | ||
120 | include: [ | ||
121 | { | ||
122 | attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ], | ||
123 | model: ActorModel.unscoped(), | ||
124 | required: true, | ||
125 | include: [ | ||
126 | { | ||
127 | attributes: [ 'host' ], | ||
128 | model: ServerModel.unscoped(), | ||
129 | required: false | ||
130 | }, | ||
131 | { | ||
132 | model: AvatarModel.unscoped(), | ||
133 | required: false | ||
134 | } | ||
135 | ] | ||
136 | } | ||
137 | ] | ||
138 | } | ||
139 | |||
140 | if (options.withAccount === true) { | ||
141 | base.include.push({ | ||
142 | model: AccountModel.scope({ | ||
143 | method: [ AccountModelScopeNames.SUMMARY, { withAccountBlockerIds: options.withAccountBlockerIds } as AccountSummaryOptions ] | ||
144 | }), | ||
145 | required: true | ||
146 | }) | ||
147 | } | ||
148 | |||
149 | return base | ||
150 | }, | ||
158 | [ScopeNames.WITH_ACCOUNT]: { | 151 | [ScopeNames.WITH_ACCOUNT]: { |
159 | include: [ | 152 | include: [ |
160 | { | 153 | { |
@@ -163,20 +156,66 @@ export type SummaryOptions = { | |||
163 | } | 156 | } |
164 | ] | 157 | ] |
165 | }, | 158 | }, |
166 | [ScopeNames.WITH_VIDEOS]: { | 159 | [ScopeNames.WITH_ACTOR]: { |
167 | include: [ | 160 | include: [ |
168 | VideoModel | 161 | ActorModel |
169 | ] | 162 | ] |
170 | }, | 163 | }, |
171 | [ScopeNames.WITH_ACTOR]: { | 164 | [ScopeNames.WITH_VIDEOS]: { |
172 | include: [ | 165 | include: [ |
173 | ActorModel | 166 | VideoModel |
174 | ] | 167 | ] |
168 | }, | ||
169 | [ScopeNames.WITH_STATS]: (options: AvailableWithStatsOptions = { daysPrior: 30 }) => { | ||
170 | const daysPrior = parseInt(options.daysPrior + '', 10) | ||
171 | |||
172 | return { | ||
173 | attributes: { | ||
174 | include: [ | ||
175 | [ | ||
176 | literal( | ||
177 | '(' + | ||
178 | `SELECT string_agg(concat_ws('|', t.day, t.views), ',') ` + | ||
179 | 'FROM ( ' + | ||
180 | 'WITH ' + | ||
181 | 'days AS ( ' + | ||
182 | `SELECT generate_series(date_trunc('day', now()) - '${daysPrior} day'::interval, ` + | ||
183 | `date_trunc('day', now()), '1 day'::interval) AS day ` + | ||
184 | '), ' + | ||
185 | 'views AS ( ' + | ||
186 | 'SELECT v.* ' + | ||
187 | 'FROM "videoView" AS v ' + | ||
188 | 'INNER JOIN "video" ON "video"."id" = v."videoId" ' + | ||
189 | 'WHERE "video"."channelId" = "VideoChannelModel"."id" ' + | ||
190 | ') ' + | ||
191 | 'SELECT days.day AS day, ' + | ||
192 | 'COALESCE(SUM(views.views), 0) AS views ' + | ||
193 | 'FROM days ' + | ||
194 | `LEFT JOIN views ON date_trunc('day', "views"."startDate") = date_trunc('day', days.day) ` + | ||
195 | 'GROUP BY day ' + | ||
196 | 'ORDER BY day ' + | ||
197 | ') t' + | ||
198 | ')' | ||
199 | ), | ||
200 | 'viewsPerDay' | ||
201 | ] | ||
202 | ] | ||
203 | } | ||
204 | } | ||
175 | } | 205 | } |
176 | })) | 206 | })) |
177 | @Table({ | 207 | @Table({ |
178 | tableName: 'videoChannel', | 208 | tableName: 'videoChannel', |
179 | indexes | 209 | indexes: [ |
210 | buildTrigramSearchIndex('video_channel_name_trigram', 'name'), | ||
211 | |||
212 | { | ||
213 | fields: [ 'accountId' ] | ||
214 | }, | ||
215 | { | ||
216 | fields: [ 'actorId' ] | ||
217 | } | ||
218 | ] | ||
180 | }) | 219 | }) |
181 | export class VideoChannelModel extends Model<VideoChannelModel> { | 220 | export class VideoChannelModel extends Model<VideoChannelModel> { |
182 | 221 | ||
@@ -351,10 +390,11 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
351 | } | 390 | } |
352 | 391 | ||
353 | static listByAccount (options: { | 392 | static listByAccount (options: { |
354 | accountId: number, | 393 | accountId: number |
355 | start: number, | 394 | start: number |
356 | count: number, | 395 | count: number |
357 | sort: string | 396 | sort: string |
397 | withStats?: boolean | ||
358 | }) { | 398 | }) { |
359 | const query = { | 399 | const query = { |
360 | offset: options.start, | 400 | offset: options.start, |
@@ -371,7 +411,16 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
371 | ] | 411 | ] |
372 | } | 412 | } |
373 | 413 | ||
414 | const scopes: string | ScopeOptions | (string | ScopeOptions)[] = [ ScopeNames.WITH_ACTOR ] | ||
415 | |||
416 | if (options.withStats) { | ||
417 | scopes.push({ | ||
418 | method: [ ScopeNames.WITH_STATS, { daysPrior: 30 } as AvailableWithStatsOptions ] | ||
419 | }) | ||
420 | } | ||
421 | |||
374 | return VideoChannelModel | 422 | return VideoChannelModel |
423 | .scope(scopes) | ||
375 | .findAndCountAll(query) | 424 | .findAndCountAll(query) |
376 | .then(({ rows, count }) => { | 425 | .then(({ rows, count }) => { |
377 | return { total: count, data: rows } | 426 | return { total: count, data: rows } |
@@ -499,6 +548,8 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
499 | } | 548 | } |
500 | 549 | ||
501 | toFormattedJSON (this: MChannelFormattable): VideoChannel { | 550 | toFormattedJSON (this: MChannelFormattable): VideoChannel { |
551 | const viewsPerDay = this.get('viewsPerDay') as string | ||
552 | |||
502 | const actor = this.Actor.toFormattedJSON() | 553 | const actor = this.Actor.toFormattedJSON() |
503 | const videoChannel = { | 554 | const videoChannel = { |
504 | id: this.id, | 555 | id: this.id, |
@@ -508,7 +559,16 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
508 | isLocal: this.Actor.isOwned(), | 559 | isLocal: this.Actor.isOwned(), |
509 | createdAt: this.createdAt, | 560 | createdAt: this.createdAt, |
510 | updatedAt: this.updatedAt, | 561 | updatedAt: this.updatedAt, |
511 | ownerAccount: undefined | 562 | ownerAccount: undefined, |
563 | viewsPerDay: viewsPerDay !== undefined | ||
564 | ? viewsPerDay.split(',').map(v => { | ||
565 | const o = v.split('|') | ||
566 | return { | ||
567 | date: new Date(o[0]), | ||
568 | views: +o[1] | ||
569 | } | ||
570 | }) | ||
571 | : undefined | ||
512 | } | 572 | } |
513 | 573 | ||
514 | if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON() | 574 | if (this.Account) videoChannel.ownerAccount = this.Account.toFormattedJSON() |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index fb4d16b4d..6d60271e6 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -9,7 +9,6 @@ import { ActorModel } from '../activitypub/actor' | |||
9 | import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils' | 9 | import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getCommentSort, throwIfNotValid } from '../utils' |
10 | import { VideoModel } from './video' | 10 | import { VideoModel } from './video' |
11 | import { VideoChannelModel } from './video-channel' | 11 | import { VideoChannelModel } from './video-channel' |
12 | import { getServerActor } from '../../helpers/utils' | ||
13 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' | 12 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' |
14 | import { regexpCapture } from '../../helpers/regexp' | 13 | import { regexpCapture } from '../../helpers/regexp' |
15 | import { uniq } from 'lodash' | 14 | import { uniq } from 'lodash' |
@@ -27,6 +26,8 @@ import { | |||
27 | MCommentOwnerVideoReply | 26 | MCommentOwnerVideoReply |
28 | } from '../../typings/models/video' | 27 | } from '../../typings/models/video' |
29 | import { MUserAccountId } from '@server/typings/models' | 28 | import { MUserAccountId } from '@server/typings/models' |
29 | import { VideoPrivacy } from '@shared/models' | ||
30 | import { getServerActor } from '@server/models/application/application' | ||
30 | 31 | ||
31 | enum ScopeNames { | 32 | enum ScopeNames { |
32 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 33 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -257,10 +258,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
257 | } | 258 | } |
258 | 259 | ||
259 | static async listThreadsForApi (parameters: { | 260 | static async listThreadsForApi (parameters: { |
260 | videoId: number, | 261 | videoId: number |
261 | start: number, | 262 | start: number |
262 | count: number, | 263 | count: number |
263 | sort: string, | 264 | sort: string |
264 | user?: MUserAccountId | 265 | user?: MUserAccountId |
265 | }) { | 266 | }) { |
266 | const { videoId, start, count, sort, user } = parameters | 267 | const { videoId, start, count, sort, user } = parameters |
@@ -300,8 +301,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
300 | } | 301 | } |
301 | 302 | ||
302 | static async listThreadCommentsForApi (parameters: { | 303 | static async listThreadCommentsForApi (parameters: { |
303 | videoId: number, | 304 | videoId: number |
304 | threadId: number, | 305 | threadId: number |
305 | user?: MUserAccountId | 306 | user?: MUserAccountId |
306 | }) { | 307 | }) { |
307 | const { videoId, threadId, user } = parameters | 308 | const { videoId, threadId, user } = parameters |
@@ -314,7 +315,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
314 | order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, | 315 | order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, |
315 | where: { | 316 | where: { |
316 | videoId, | 317 | videoId, |
317 | [ Op.or ]: [ | 318 | [Op.or]: [ |
318 | { id: threadId }, | 319 | { id: threadId }, |
319 | { originCommentId: threadId } | 320 | { originCommentId: threadId } |
320 | ], | 321 | ], |
@@ -346,7 +347,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
346 | order: [ [ 'createdAt', order ] ] as Order, | 347 | order: [ [ 'createdAt', order ] ] as Order, |
347 | where: { | 348 | where: { |
348 | id: { | 349 | id: { |
349 | [ Op.in ]: Sequelize.literal('(' + | 350 | [Op.in]: Sequelize.literal('(' + |
350 | 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + | 351 | 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + |
351 | `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + | 352 | `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + |
352 | 'UNION ' + | 353 | 'UNION ' + |
@@ -355,7 +356,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
355 | ') ' + | 356 | ') ' + |
356 | 'SELECT id FROM children' + | 357 | 'SELECT id FROM children' + |
357 | ')'), | 358 | ')'), |
358 | [ Op.ne ]: comment.id | 359 | [Op.ne]: comment.id |
359 | } | 360 | } |
360 | }, | 361 | }, |
361 | transaction: t | 362 | transaction: t |
@@ -380,17 +381,29 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
380 | return VideoCommentModel.findAndCountAll<MComment>(query) | 381 | return VideoCommentModel.findAndCountAll<MComment>(query) |
381 | } | 382 | } |
382 | 383 | ||
383 | static listForFeed (start: number, count: number, videoId?: number): Bluebird<MCommentOwnerVideoFeed[]> { | 384 | static async listForFeed (start: number, count: number, videoId?: number): Promise<MCommentOwnerVideoFeed[]> { |
385 | const serverActor = await getServerActor() | ||
386 | |||
384 | const query = { | 387 | const query = { |
385 | order: [ [ 'createdAt', 'DESC' ] ] as Order, | 388 | order: [ [ 'createdAt', 'DESC' ] ] as Order, |
386 | offset: start, | 389 | offset: start, |
387 | limit: count, | 390 | limit: count, |
388 | where: {}, | 391 | where: { |
392 | deletedAt: null, | ||
393 | accountId: { | ||
394 | [Op.notIn]: Sequelize.literal( | ||
395 | '(' + buildBlockedAccountSQL(serverActor.Account.id) + ')' | ||
396 | ) | ||
397 | } | ||
398 | }, | ||
389 | include: [ | 399 | include: [ |
390 | { | 400 | { |
391 | attributes: [ 'name', 'uuid' ], | 401 | attributes: [ 'name', 'uuid' ], |
392 | model: VideoModel.unscoped(), | 402 | model: VideoModel.unscoped(), |
393 | required: true | 403 | required: true, |
404 | where: { | ||
405 | privacy: VideoPrivacy.PUBLIC | ||
406 | } | ||
394 | } | 407 | } |
395 | ] | 408 | ] |
396 | } | 409 | } |
@@ -461,7 +474,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
461 | } | 474 | } |
462 | 475 | ||
463 | isDeleted () { | 476 | isDeleted () { |
464 | return null !== this.deletedAt | 477 | return this.deletedAt !== null |
465 | } | 478 | } |
466 | 479 | ||
467 | extractMentions () { | 480 | extractMentions () { |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index e08999385..201f0c0f1 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -10,7 +10,9 @@ import { | |||
10 | Is, | 10 | Is, |
11 | Model, | 11 | Model, |
12 | Table, | 12 | Table, |
13 | UpdatedAt | 13 | UpdatedAt, |
14 | Scopes, | ||
15 | DefaultScope | ||
14 | } from 'sequelize-typescript' | 16 | } from 'sequelize-typescript' |
15 | import { | 17 | import { |
16 | isVideoFileExtnameValid, | 18 | isVideoFileExtnameValid, |
@@ -28,7 +30,33 @@ import { MIMETYPES, MEMOIZE_LENGTH, MEMOIZE_TTL } from '../../initializers/const | |||
28 | import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../typings/models/video/video-file' | 30 | import { MVideoFile, MVideoFileStreamingPlaylistVideo, MVideoFileVideo } from '../../typings/models/video/video-file' |
29 | import { MStreamingPlaylistVideo, MVideo } from '@server/typings/models' | 31 | import { MStreamingPlaylistVideo, MVideo } from '@server/typings/models' |
30 | import * as memoizee from 'memoizee' | 32 | import * as memoizee from 'memoizee' |
33 | import validator from 'validator' | ||
31 | 34 | ||
35 | export enum ScopeNames { | ||
36 | WITH_VIDEO = 'WITH_VIDEO', | ||
37 | WITH_METADATA = 'WITH_METADATA' | ||
38 | } | ||
39 | |||
40 | @DefaultScope(() => ({ | ||
41 | attributes: { | ||
42 | exclude: [ 'metadata' ] | ||
43 | } | ||
44 | })) | ||
45 | @Scopes(() => ({ | ||
46 | [ScopeNames.WITH_VIDEO]: { | ||
47 | include: [ | ||
48 | { | ||
49 | model: VideoModel.unscoped(), | ||
50 | required: true | ||
51 | } | ||
52 | ] | ||
53 | }, | ||
54 | [ScopeNames.WITH_METADATA]: { | ||
55 | attributes: { | ||
56 | include: [ 'metadata' ] | ||
57 | } | ||
58 | } | ||
59 | })) | ||
32 | @Table({ | 60 | @Table({ |
33 | tableName: 'videoFile', | 61 | tableName: 'videoFile', |
34 | indexes: [ | 62 | indexes: [ |
@@ -106,6 +134,14 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
106 | @Column | 134 | @Column |
107 | fps: number | 135 | fps: number |
108 | 136 | ||
137 | @AllowNull(true) | ||
138 | @Column(DataType.JSONB) | ||
139 | metadata: any | ||
140 | |||
141 | @AllowNull(true) | ||
142 | @Column | ||
143 | metadataUrl: string | ||
144 | |||
109 | @ForeignKey(() => VideoModel) | 145 | @ForeignKey(() => VideoModel) |
110 | @Column | 146 | @Column |
111 | videoId: number | 147 | videoId: number |
@@ -157,17 +193,56 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
157 | .then(results => results.length === 1) | 193 | .then(results => results.length === 1) |
158 | } | 194 | } |
159 | 195 | ||
196 | static async doesVideoExistForVideoFile (id: number, videoIdOrUUID: number | string) { | ||
197 | const videoFile = await VideoFileModel.loadWithVideoOrPlaylist(id, videoIdOrUUID) | ||
198 | |||
199 | return !!videoFile | ||
200 | } | ||
201 | |||
202 | static loadWithMetadata (id: number) { | ||
203 | return VideoFileModel.scope(ScopeNames.WITH_METADATA).findByPk(id) | ||
204 | } | ||
205 | |||
160 | static loadWithVideo (id: number) { | 206 | static loadWithVideo (id: number) { |
207 | return VideoFileModel.scope(ScopeNames.WITH_VIDEO).findByPk(id) | ||
208 | } | ||
209 | |||
210 | static loadWithVideoOrPlaylist (id: number, videoIdOrUUID: number | string) { | ||
211 | const whereVideo = validator.isUUID(videoIdOrUUID + '') | ||
212 | ? { uuid: videoIdOrUUID } | ||
213 | : { id: videoIdOrUUID } | ||
214 | |||
161 | const options = { | 215 | const options = { |
216 | where: { | ||
217 | id | ||
218 | }, | ||
162 | include: [ | 219 | include: [ |
163 | { | 220 | { |
164 | model: VideoModel.unscoped(), | 221 | model: VideoModel.unscoped(), |
165 | required: true | 222 | required: false, |
223 | where: whereVideo | ||
224 | }, | ||
225 | { | ||
226 | model: VideoStreamingPlaylistModel.unscoped(), | ||
227 | required: false, | ||
228 | include: [ | ||
229 | { | ||
230 | model: VideoModel.unscoped(), | ||
231 | required: true, | ||
232 | where: whereVideo | ||
233 | } | ||
234 | ] | ||
166 | } | 235 | } |
167 | ] | 236 | ] |
168 | } | 237 | } |
169 | 238 | ||
170 | return VideoFileModel.findByPk(id, options) | 239 | return VideoFileModel.findOne(options) |
240 | .then(file => { | ||
241 | // We used `required: false` so check we have at least a video or a streaming playlist | ||
242 | if (!file.Video && !file.VideoStreamingPlaylist) return null | ||
243 | |||
244 | return file | ||
245 | }) | ||
171 | } | 246 | } |
172 | 247 | ||
173 | static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) { | 248 | static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) { |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 67395e5c0..d71a3a5db 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -8,7 +8,7 @@ import { | |||
8 | getVideoDislikesActivityPubUrl, | 8 | getVideoDislikesActivityPubUrl, |
9 | getVideoLikesActivityPubUrl, | 9 | getVideoLikesActivityPubUrl, |
10 | getVideoSharesActivityPubUrl | 10 | getVideoSharesActivityPubUrl |
11 | } from '../../lib/activitypub' | 11 | } from '../../lib/activitypub/url' |
12 | import { isArray } from '../../helpers/custom-validators/misc' | 12 | import { isArray } from '../../helpers/custom-validators/misc' |
13 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' | 13 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' |
14 | import { | 14 | import { |
@@ -23,16 +23,18 @@ import { | |||
23 | import { MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file' | 23 | import { MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file' |
24 | import { VideoFile } from '@shared/models/videos/video-file.model' | 24 | import { VideoFile } from '@shared/models/videos/video-file.model' |
25 | import { generateMagnetUri } from '@server/helpers/webtorrent' | 25 | import { generateMagnetUri } from '@server/helpers/webtorrent' |
26 | import { extractVideo } from '@server/helpers/video' | ||
26 | 27 | ||
27 | export type VideoFormattingJSONOptions = { | 28 | export type VideoFormattingJSONOptions = { |
28 | completeDescription?: boolean | 29 | completeDescription?: boolean |
29 | additionalAttributes: { | 30 | additionalAttributes: { |
30 | state?: boolean, | 31 | state?: boolean |
31 | waitTranscoding?: boolean, | 32 | waitTranscoding?: boolean |
32 | scheduledUpdate?: boolean, | 33 | scheduledUpdate?: boolean |
33 | blacklistInfo?: boolean | 34 | blacklistInfo?: boolean |
34 | } | 35 | } |
35 | } | 36 | } |
37 | |||
36 | function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { | 38 | function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { |
37 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined | 39 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined |
38 | 40 | ||
@@ -179,14 +181,14 @@ function videoFilesModelToFormattedJSON ( | |||
179 | baseUrlWs: string, | 181 | baseUrlWs: string, |
180 | videoFiles: MVideoFileRedundanciesOpt[] | 182 | videoFiles: MVideoFileRedundanciesOpt[] |
181 | ): VideoFile[] { | 183 | ): VideoFile[] { |
184 | const video = extractVideo(model) | ||
185 | |||
182 | return videoFiles | 186 | return videoFiles |
183 | .map(videoFile => { | 187 | .map(videoFile => { |
184 | let resolutionLabel = videoFile.resolution + 'p' | ||
185 | |||
186 | return { | 188 | return { |
187 | resolution: { | 189 | resolution: { |
188 | id: videoFile.resolution, | 190 | id: videoFile.resolution, |
189 | label: resolutionLabel | 191 | label: videoFile.resolution + 'p' |
190 | }, | 192 | }, |
191 | magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs), | 193 | magnetUri: generateMagnetUri(model, videoFile, baseUrlHttp, baseUrlWs), |
192 | size: videoFile.size, | 194 | size: videoFile.size, |
@@ -194,7 +196,8 @@ function videoFilesModelToFormattedJSON ( | |||
194 | torrentUrl: model.getTorrentUrl(videoFile, baseUrlHttp), | 196 | torrentUrl: model.getTorrentUrl(videoFile, baseUrlHttp), |
195 | torrentDownloadUrl: model.getTorrentDownloadUrl(videoFile, baseUrlHttp), | 197 | torrentDownloadUrl: model.getTorrentDownloadUrl(videoFile, baseUrlHttp), |
196 | fileUrl: model.getVideoFileUrl(videoFile, baseUrlHttp), | 198 | fileUrl: model.getVideoFileUrl(videoFile, baseUrlHttp), |
197 | fileDownloadUrl: model.getVideoFileDownloadUrl(videoFile, baseUrlHttp) | 199 | fileDownloadUrl: model.getVideoFileDownloadUrl(videoFile, baseUrlHttp), |
200 | metadataUrl: video.getVideoFileMetadataUrl(videoFile, baseUrlHttp) | ||
198 | } as VideoFile | 201 | } as VideoFile |
199 | }) | 202 | }) |
200 | .sort((a, b) => { | 203 | .sort((a, b) => { |
@@ -214,7 +217,7 @@ function addVideoFilesInAPAcc ( | |||
214 | for (const file of files) { | 217 | for (const file of files) { |
215 | acc.push({ | 218 | acc.push({ |
216 | type: 'Link', | 219 | type: 'Link', |
217 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[ file.extname ] as any, | 220 | mediaType: MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] as any, |
218 | href: model.getVideoFileUrl(file, baseUrlHttp), | 221 | href: model.getVideoFileUrl(file, baseUrlHttp), |
219 | height: file.resolution, | 222 | height: file.resolution, |
220 | size: file.size, | 223 | size: file.size, |
@@ -223,6 +226,15 @@ function addVideoFilesInAPAcc ( | |||
223 | 226 | ||
224 | acc.push({ | 227 | acc.push({ |
225 | type: 'Link', | 228 | type: 'Link', |
229 | rel: [ 'metadata', MIMETYPES.VIDEO.EXT_MIMETYPE[file.extname] ], | ||
230 | mediaType: 'application/json' as 'application/json', | ||
231 | href: extractVideo(model).getVideoFileMetadataUrl(file, baseUrlHttp), | ||
232 | height: file.resolution, | ||
233 | fps: file.fps | ||
234 | }) | ||
235 | |||
236 | acc.push({ | ||
237 | type: 'Link', | ||
226 | mediaType: 'application/x-bittorrent' as 'application/x-bittorrent', | 238 | mediaType: 'application/x-bittorrent' as 'application/x-bittorrent', |
227 | href: model.getTorrentUrl(file, baseUrlHttp), | 239 | href: model.getTorrentUrl(file, baseUrlHttp), |
228 | height: file.resolution | 240 | height: file.resolution |
@@ -282,10 +294,8 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { | |||
282 | addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) | 294 | addVideoFilesInAPAcc(url, video, baseUrlHttp, baseUrlWs, video.VideoFiles || []) |
283 | 295 | ||
284 | for (const playlist of (video.VideoStreamingPlaylists || [])) { | 296 | for (const playlist of (video.VideoStreamingPlaylists || [])) { |
285 | let tag: ActivityTagObject[] | 297 | const tag = playlist.p2pMediaLoaderInfohashes |
286 | 298 | .map(i => ({ type: 'Infohash' as 'Infohash', name: i })) as ActivityTagObject[] | |
287 | tag = playlist.p2pMediaLoaderInfohashes | ||
288 | .map(i => ({ type: 'Infohash' as 'Infohash', name: i })) | ||
289 | tag.push({ | 299 | tag.push({ |
290 | type: 'Link', | 300 | type: 'Link', |
291 | name: 'sha256', | 301 | name: 'sha256', |
@@ -308,10 +318,14 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { | |||
308 | for (const caption of video.VideoCaptions) { | 318 | for (const caption of video.VideoCaptions) { |
309 | subtitleLanguage.push({ | 319 | subtitleLanguage.push({ |
310 | identifier: caption.language, | 320 | identifier: caption.language, |
311 | name: VideoCaptionModel.getLanguageLabel(caption.language) | 321 | name: VideoCaptionModel.getLanguageLabel(caption.language), |
322 | url: caption.getFileUrl(video) | ||
312 | }) | 323 | }) |
313 | } | 324 | } |
314 | 325 | ||
326 | // FIXME: remove and uncomment in PT 2.3 | ||
327 | // Breaks compatibility with PT <= 2.1 | ||
328 | // const icons = [ video.getMiniature(), video.getPreview() ] | ||
315 | const miniature = video.getMiniature() | 329 | const miniature = video.getMiniature() |
316 | 330 | ||
317 | return { | 331 | return { |
@@ -339,11 +353,18 @@ function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { | |||
339 | subtitleLanguage, | 353 | subtitleLanguage, |
340 | icon: { | 354 | icon: { |
341 | type: 'Image', | 355 | type: 'Image', |
342 | url: miniature.getFileUrl(video.isOwned()), | 356 | url: miniature.getFileUrl(video), |
343 | mediaType: 'image/jpeg', | 357 | mediaType: 'image/jpeg', |
344 | width: miniature.width, | 358 | width: miniature.width, |
345 | height: miniature.height | 359 | height: miniature.height |
346 | }, | 360 | } as any, |
361 | // icon: icons.map(i => ({ | ||
362 | // type: 'Image', | ||
363 | // url: i.getFileUrl(video), | ||
364 | // mediaType: 'image/jpeg', | ||
365 | // width: i.width, | ||
366 | // height: i.height | ||
367 | // })), | ||
347 | url, | 368 | url, |
348 | likes: getVideoLikesActivityPubUrl(video), | 369 | likes: getVideoLikesActivityPubUrl(video), |
349 | dislikes: getVideoDislikesActivityPubUrl(video), | 370 | dislikes: getVideoDislikesActivityPubUrl(video), |
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index af5314ce9..fbe0ee0a7 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -129,6 +129,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
129 | distinct: true, | 129 | distinct: true, |
130 | include: [ | 130 | include: [ |
131 | { | 131 | { |
132 | attributes: [ 'id' ], | ||
132 | model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query | 133 | model: UserModel.unscoped(), // FIXME: Without this, sequelize try to COUNT(DISTINCT(*)) which is an invalid SQL query |
133 | required: true | 134 | required: true |
134 | } | 135 | } |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index f2d71357f..9ea73e82e 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -120,10 +120,10 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
120 | } | 120 | } |
121 | 121 | ||
122 | static listForApi (options: { | 122 | static listForApi (options: { |
123 | start: number, | 123 | start: number |
124 | count: number, | 124 | count: number |
125 | videoPlaylistId: number, | 125 | videoPlaylistId: number |
126 | serverAccount: AccountModel, | 126 | serverAccount: AccountModel |
127 | user?: MUserAccountId | 127 | user?: MUserAccountId |
128 | }) { | 128 | }) { |
129 | const accountIds = [ options.serverAccount.id ] | 129 | const accountIds = [ options.serverAccount.id ] |
@@ -309,7 +309,10 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
309 | // Owned video, don't filter it | 309 | // Owned video, don't filter it |
310 | if (accountId && video.VideoChannel.Account.id === accountId) return VideoPlaylistElementType.REGULAR | 310 | if (accountId && video.VideoChannel.Account.id === accountId) return VideoPlaylistElementType.REGULAR |
311 | 311 | ||
312 | if (video.privacy === VideoPrivacy.PRIVATE) return VideoPlaylistElementType.PRIVATE | 312 | // Internal video? |
313 | if (video.privacy === VideoPrivacy.INTERNAL && accountId) return VideoPlaylistElementType.REGULAR | ||
314 | |||
315 | if (video.privacy === VideoPrivacy.PRIVATE || video.privacy === VideoPrivacy.INTERNAL) return VideoPlaylistElementType.PRIVATE | ||
313 | 316 | ||
314 | if (video.isBlacklisted() || video.isBlocked()) return VideoPlaylistElementType.UNAVAILABLE | 317 | if (video.isBlacklisted() || video.isBlocked()) return VideoPlaylistElementType.UNAVAILABLE |
315 | if (video.nsfw === true && displayNSFW === false) return VideoPlaylistElementType.UNAVAILABLE | 318 | if (video.nsfw === true && displayNSFW === false) return VideoPlaylistElementType.UNAVAILABLE |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index bcdda36e5..b9b95e067 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -68,12 +68,12 @@ type AvailableForListOptions = { | |||
68 | type?: VideoPlaylistType | 68 | type?: VideoPlaylistType |
69 | accountId?: number | 69 | accountId?: number |
70 | videoChannelId?: number | 70 | videoChannelId?: number |
71 | listMyPlaylists?: boolean, | 71 | listMyPlaylists?: boolean |
72 | search?: string | 72 | search?: string |
73 | } | 73 | } |
74 | 74 | ||
75 | @Scopes(() => ({ | 75 | @Scopes(() => ({ |
76 | [ ScopeNames.WITH_THUMBNAIL ]: { | 76 | [ScopeNames.WITH_THUMBNAIL]: { |
77 | include: [ | 77 | include: [ |
78 | { | 78 | { |
79 | model: ThumbnailModel, | 79 | model: ThumbnailModel, |
@@ -81,7 +81,7 @@ type AvailableForListOptions = { | |||
81 | } | 81 | } |
82 | ] | 82 | ] |
83 | }, | 83 | }, |
84 | [ ScopeNames.WITH_VIDEOS_LENGTH ]: { | 84 | [ScopeNames.WITH_VIDEOS_LENGTH]: { |
85 | attributes: { | 85 | attributes: { |
86 | include: [ | 86 | include: [ |
87 | [ | 87 | [ |
@@ -91,7 +91,7 @@ type AvailableForListOptions = { | |||
91 | ] | 91 | ] |
92 | } | 92 | } |
93 | } as FindOptions, | 93 | } as FindOptions, |
94 | [ ScopeNames.WITH_ACCOUNT ]: { | 94 | [ScopeNames.WITH_ACCOUNT]: { |
95 | include: [ | 95 | include: [ |
96 | { | 96 | { |
97 | model: AccountModel, | 97 | model: AccountModel, |
@@ -99,7 +99,7 @@ type AvailableForListOptions = { | |||
99 | } | 99 | } |
100 | ] | 100 | ] |
101 | }, | 101 | }, |
102 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { | 102 | [ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY]: { |
103 | include: [ | 103 | include: [ |
104 | { | 104 | { |
105 | model: AccountModel.scope(AccountScopeNames.SUMMARY), | 105 | model: AccountModel.scope(AccountScopeNames.SUMMARY), |
@@ -111,7 +111,7 @@ type AvailableForListOptions = { | |||
111 | } | 111 | } |
112 | ] | 112 | ] |
113 | }, | 113 | }, |
114 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { | 114 | [ScopeNames.WITH_ACCOUNT_AND_CHANNEL]: { |
115 | include: [ | 115 | include: [ |
116 | { | 116 | { |
117 | model: AccountModel, | 117 | model: AccountModel, |
@@ -123,7 +123,7 @@ type AvailableForListOptions = { | |||
123 | } | 123 | } |
124 | ] | 124 | ] |
125 | }, | 125 | }, |
126 | [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => { | 126 | [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { |
127 | 127 | ||
128 | let whereActor: WhereOptions = {} | 128 | let whereActor: WhereOptions = {} |
129 | 129 | ||
@@ -138,13 +138,13 @@ type AvailableForListOptions = { | |||
138 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) | 138 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) |
139 | 139 | ||
140 | whereActor = { | 140 | whereActor = { |
141 | [ Op.or ]: [ | 141 | [Op.or]: [ |
142 | { | 142 | { |
143 | serverId: null | 143 | serverId: null |
144 | }, | 144 | }, |
145 | { | 145 | { |
146 | serverId: { | 146 | serverId: { |
147 | [ Op.in ]: literal(inQueryInstanceFollow) | 147 | [Op.in]: literal(inQueryInstanceFollow) |
148 | } | 148 | } |
149 | } | 149 | } |
150 | ] | 150 | ] |
@@ -172,7 +172,7 @@ type AvailableForListOptions = { | |||
172 | if (options.search) { | 172 | if (options.search) { |
173 | whereAnd.push({ | 173 | whereAnd.push({ |
174 | name: { | 174 | name: { |
175 | [ Op.iLike ]: '%' + options.search + '%' | 175 | [Op.iLike]: '%' + options.search + '%' |
176 | } | 176 | } |
177 | }) | 177 | }) |
178 | } | 178 | } |
@@ -230,7 +230,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
230 | 230 | ||
231 | @AllowNull(true) | 231 | @AllowNull(true) |
232 | @Is('VideoPlaylistDescription', value => throwIfNotValid(value, isVideoPlaylistDescriptionValid, 'description', true)) | 232 | @Is('VideoPlaylistDescription', value => throwIfNotValid(value, isVideoPlaylistDescriptionValid, 'description', true)) |
233 | @Column | 233 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.DESCRIPTION.max)) |
234 | description: string | 234 | description: string |
235 | 235 | ||
236 | @AllowNull(false) | 236 | @AllowNull(false) |
@@ -299,13 +299,13 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
299 | 299 | ||
300 | static listForApi (options: { | 300 | static listForApi (options: { |
301 | followerActorId: number | 301 | followerActorId: number |
302 | start: number, | 302 | start: number |
303 | count: number, | 303 | count: number |
304 | sort: string, | 304 | sort: string |
305 | type?: VideoPlaylistType, | 305 | type?: VideoPlaylistType |
306 | accountId?: number, | 306 | accountId?: number |
307 | videoChannelId?: number, | 307 | videoChannelId?: number |
308 | listMyPlaylists?: boolean, | 308 | listMyPlaylists?: boolean |
309 | search?: string | 309 | search?: string |
310 | }) { | 310 | }) { |
311 | const query = { | 311 | const query = { |
@@ -369,7 +369,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
369 | model: VideoPlaylistElementModel.unscoped(), | 369 | model: VideoPlaylistElementModel.unscoped(), |
370 | where: { | 370 | where: { |
371 | videoId: { | 371 | videoId: { |
372 | [Op.in]: videoIds // FIXME: sequelize ANY seems broken | 372 | [Op.in]: videoIds |
373 | } | 373 | } |
374 | }, | 374 | }, |
375 | required: true | 375 | required: true |
@@ -522,7 +522,9 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
522 | updatedAt: this.updatedAt, | 522 | updatedAt: this.updatedAt, |
523 | 523 | ||
524 | ownerAccount: this.OwnerAccount.toFormattedSummaryJSON(), | 524 | ownerAccount: this.OwnerAccount.toFormattedSummaryJSON(), |
525 | videoChannel: this.VideoChannel ? this.VideoChannel.toFormattedSummaryJSON() : null | 525 | videoChannel: this.VideoChannel |
526 | ? this.VideoChannel.toFormattedSummaryJSON() | ||
527 | : null | ||
526 | } | 528 | } |
527 | } | 529 | } |
528 | 530 | ||
diff --git a/server/models/video/video-query-builder.ts b/server/models/video/video-query-builder.ts new file mode 100644 index 000000000..455f9f30f --- /dev/null +++ b/server/models/video/video-query-builder.ts | |||
@@ -0,0 +1,503 @@ | |||
1 | import { VideoFilter, VideoPrivacy, VideoState } from '@shared/models' | ||
2 | import { buildDirectionAndField, createSafeIn } from '@server/models/utils' | ||
3 | import { Model } from 'sequelize-typescript' | ||
4 | import { MUserAccountId, MUserId } from '@server/typings/models' | ||
5 | import validator from 'validator' | ||
6 | import { exists } from '@server/helpers/custom-validators/misc' | ||
7 | |||
8 | export type BuildVideosQueryOptions = { | ||
9 | attributes?: string[] | ||
10 | |||
11 | serverAccountId: number | ||
12 | followerActorId: number | ||
13 | includeLocalVideos: boolean | ||
14 | |||
15 | count: number | ||
16 | start: number | ||
17 | sort: string | ||
18 | |||
19 | filter?: VideoFilter | ||
20 | categoryOneOf?: number[] | ||
21 | nsfw?: boolean | ||
22 | licenceOneOf?: number[] | ||
23 | languageOneOf?: string[] | ||
24 | tagsOneOf?: string[] | ||
25 | tagsAllOf?: string[] | ||
26 | |||
27 | withFiles?: boolean | ||
28 | |||
29 | accountId?: number | ||
30 | videoChannelId?: number | ||
31 | |||
32 | videoPlaylistId?: number | ||
33 | |||
34 | trendingDays?: number | ||
35 | user?: MUserAccountId | ||
36 | historyOfUser?: MUserId | ||
37 | |||
38 | startDate?: string // ISO 8601 | ||
39 | endDate?: string // ISO 8601 | ||
40 | originallyPublishedStartDate?: string | ||
41 | originallyPublishedEndDate?: string | ||
42 | |||
43 | durationMin?: number // seconds | ||
44 | durationMax?: number // seconds | ||
45 | |||
46 | search?: string | ||
47 | |||
48 | isCount?: boolean | ||
49 | |||
50 | group?: string | ||
51 | having?: string | ||
52 | } | ||
53 | |||
54 | function buildListQuery (model: typeof Model, options: BuildVideosQueryOptions) { | ||
55 | const and: string[] = [] | ||
56 | const joins: string[] = [] | ||
57 | const replacements: any = {} | ||
58 | const cte: string[] = [] | ||
59 | |||
60 | let attributes: string[] = options.attributes || [ '"video"."id"' ] | ||
61 | let group = options.group || '' | ||
62 | const having = options.having || '' | ||
63 | |||
64 | joins.push( | ||
65 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId"' + | ||
66 | 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId"' + | ||
67 | 'INNER JOIN "actor" "accountActor" ON "account"."actorId" = "accountActor"."id"' | ||
68 | ) | ||
69 | |||
70 | and.push('"video"."id" NOT IN (SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")') | ||
71 | |||
72 | if (options.serverAccountId) { | ||
73 | const blockerIds = [ options.serverAccountId ] | ||
74 | if (options.user) blockerIds.push(options.user.Account.id) | ||
75 | |||
76 | const inClause = createSafeIn(model, blockerIds) | ||
77 | |||
78 | and.push( | ||
79 | 'NOT EXISTS (' + | ||
80 | ' SELECT 1 FROM "accountBlocklist" ' + | ||
81 | ' WHERE "accountBlocklist"."accountId" IN (' + inClause + ') ' + | ||
82 | ' AND "accountBlocklist"."targetAccountId" = "account"."id" ' + | ||
83 | ')' + | ||
84 | 'AND NOT EXISTS (' + | ||
85 | ' SELECT 1 FROM "serverBlocklist" WHERE "serverBlocklist"."accountId" IN (' + inClause + ') ' + | ||
86 | ' AND "serverBlocklist"."targetServerId" = "accountActor"."serverId"' + | ||
87 | ')' | ||
88 | ) | ||
89 | } | ||
90 | |||
91 | // Only list public/published videos | ||
92 | if (!options.filter || options.filter !== 'all-local') { | ||
93 | and.push( | ||
94 | `("video"."state" = ${VideoState.PUBLISHED} OR ` + | ||
95 | `("video"."state" = ${VideoState.TO_TRANSCODE} AND "video"."waitTranscoding" IS false))` | ||
96 | ) | ||
97 | |||
98 | if (options.user) { | ||
99 | and.push( | ||
100 | `("video"."privacy" = ${VideoPrivacy.PUBLIC} OR "video"."privacy" = ${VideoPrivacy.INTERNAL})` | ||
101 | ) | ||
102 | } else { // Or only public videos | ||
103 | and.push( | ||
104 | `"video"."privacy" = ${VideoPrivacy.PUBLIC}` | ||
105 | ) | ||
106 | } | ||
107 | } | ||
108 | |||
109 | if (options.videoPlaylistId) { | ||
110 | joins.push( | ||
111 | 'INNER JOIN "videoPlaylistElement" "video"."id" = "videoPlaylistElement"."videoId" ' + | ||
112 | 'AND "videoPlaylistElement"."videoPlaylistId" = :videoPlaylistId' | ||
113 | ) | ||
114 | |||
115 | replacements.videoPlaylistId = options.videoPlaylistId | ||
116 | } | ||
117 | |||
118 | if (options.filter && (options.filter === 'local' || options.filter === 'all-local')) { | ||
119 | and.push('"video"."remote" IS FALSE') | ||
120 | } | ||
121 | |||
122 | if (options.accountId) { | ||
123 | and.push('"account"."id" = :accountId') | ||
124 | replacements.accountId = options.accountId | ||
125 | } | ||
126 | |||
127 | if (options.videoChannelId) { | ||
128 | and.push('"videoChannel"."id" = :videoChannelId') | ||
129 | replacements.videoChannelId = options.videoChannelId | ||
130 | } | ||
131 | |||
132 | if (options.followerActorId) { | ||
133 | let query = | ||
134 | '(' + | ||
135 | ' EXISTS (' + | ||
136 | ' SELECT 1 FROM "videoShare" ' + | ||
137 | ' INNER JOIN "actorFollow" "actorFollowShare" ON "actorFollowShare"."targetActorId" = "videoShare"."actorId" ' + | ||
138 | ' AND "actorFollowShare"."actorId" = :followerActorId WHERE "videoShare"."videoId" = "video"."id"' + | ||
139 | ' )' + | ||
140 | ' OR' + | ||
141 | ' EXISTS (' + | ||
142 | ' SELECT 1 from "actorFollow" ' + | ||
143 | ' WHERE "actorFollow"."targetActorId" = "videoChannel"."actorId" AND "actorFollow"."actorId" = :followerActorId' + | ||
144 | ' )' | ||
145 | |||
146 | if (options.includeLocalVideos) { | ||
147 | query += ' OR "video"."remote" IS FALSE' | ||
148 | } | ||
149 | |||
150 | query += ')' | ||
151 | |||
152 | and.push(query) | ||
153 | replacements.followerActorId = options.followerActorId | ||
154 | } | ||
155 | |||
156 | if (options.withFiles === true) { | ||
157 | and.push('EXISTS (SELECT 1 FROM "videoFile" WHERE "videoFile"."videoId" = "video"."id")') | ||
158 | } | ||
159 | |||
160 | if (options.tagsOneOf) { | ||
161 | const tagsOneOfLower = options.tagsOneOf.map(t => t.toLowerCase()) | ||
162 | |||
163 | and.push( | ||
164 | 'EXISTS (' + | ||
165 | ' SELECT 1 FROM "videoTag" ' + | ||
166 | ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | ||
167 | ' WHERE lower("tag"."name") IN (' + createSafeIn(model, tagsOneOfLower) + ') ' + | ||
168 | ' AND "video"."id" = "videoTag"."videoId"' + | ||
169 | ')' | ||
170 | ) | ||
171 | } | ||
172 | |||
173 | if (options.tagsAllOf) { | ||
174 | const tagsAllOfLower = options.tagsAllOf.map(t => t.toLowerCase()) | ||
175 | |||
176 | and.push( | ||
177 | 'EXISTS (' + | ||
178 | ' SELECT 1 FROM "videoTag" ' + | ||
179 | ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | ||
180 | ' WHERE lower("tag"."name") IN (' + createSafeIn(model, tagsAllOfLower) + ') ' + | ||
181 | ' AND "video"."id" = "videoTag"."videoId" ' + | ||
182 | ' GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length + | ||
183 | ')' | ||
184 | ) | ||
185 | } | ||
186 | |||
187 | if (options.nsfw === true) { | ||
188 | and.push('"video"."nsfw" IS TRUE') | ||
189 | } | ||
190 | |||
191 | if (options.nsfw === false) { | ||
192 | and.push('"video"."nsfw" IS FALSE') | ||
193 | } | ||
194 | |||
195 | if (options.categoryOneOf) { | ||
196 | and.push('"video"."category" IN (:categoryOneOf)') | ||
197 | replacements.categoryOneOf = options.categoryOneOf | ||
198 | } | ||
199 | |||
200 | if (options.licenceOneOf) { | ||
201 | and.push('"video"."licence" IN (:licenceOneOf)') | ||
202 | replacements.licenceOneOf = options.licenceOneOf | ||
203 | } | ||
204 | |||
205 | if (options.languageOneOf) { | ||
206 | const languages = options.languageOneOf.filter(l => l && l !== '_unknown') | ||
207 | const languagesQueryParts: string[] = [] | ||
208 | |||
209 | if (languages.length !== 0) { | ||
210 | languagesQueryParts.push('"video"."language" IN (:languageOneOf)') | ||
211 | replacements.languageOneOf = languages | ||
212 | |||
213 | languagesQueryParts.push( | ||
214 | 'EXISTS (' + | ||
215 | ' SELECT 1 FROM "videoCaption" WHERE "videoCaption"."language" ' + | ||
216 | ' IN (' + createSafeIn(model, languages) + ') AND ' + | ||
217 | ' "videoCaption"."videoId" = "video"."id"' + | ||
218 | ')' | ||
219 | ) | ||
220 | } | ||
221 | |||
222 | if (options.languageOneOf.includes('_unknown')) { | ||
223 | languagesQueryParts.push('"video"."language" IS NULL') | ||
224 | } | ||
225 | |||
226 | if (languagesQueryParts.length !== 0) { | ||
227 | and.push('(' + languagesQueryParts.join(' OR ') + ')') | ||
228 | } | ||
229 | } | ||
230 | |||
231 | // We don't exclude results in this if so if we do a count we don't need to add this complex clauses | ||
232 | if (options.trendingDays && options.isCount !== true) { | ||
233 | const viewsGteDate = new Date(new Date().getTime() - (24 * 3600 * 1000) * options.trendingDays) | ||
234 | |||
235 | joins.push('LEFT JOIN "videoView" ON "video"."id" = "videoView"."videoId" AND "videoView"."startDate" >= :viewsGteDate') | ||
236 | replacements.viewsGteDate = viewsGteDate | ||
237 | |||
238 | attributes.push('COALESCE(SUM("videoView"."views"), 0) AS "videoViewsSum"') | ||
239 | |||
240 | group = 'GROUP BY "video"."id"' | ||
241 | } | ||
242 | |||
243 | if (options.historyOfUser) { | ||
244 | joins.push('INNER JOIN "userVideoHistory" on "video"."id" = "userVideoHistory"."videoId"') | ||
245 | |||
246 | and.push('"userVideoHistory"."userId" = :historyOfUser') | ||
247 | replacements.historyOfUser = options.historyOfUser.id | ||
248 | } | ||
249 | |||
250 | if (options.startDate) { | ||
251 | and.push('"video"."publishedAt" >= :startDate') | ||
252 | replacements.startDate = options.startDate | ||
253 | } | ||
254 | |||
255 | if (options.endDate) { | ||
256 | and.push('"video"."publishedAt" <= :endDate') | ||
257 | replacements.endDate = options.endDate | ||
258 | } | ||
259 | |||
260 | if (options.originallyPublishedStartDate) { | ||
261 | and.push('"video"."originallyPublishedAt" >= :originallyPublishedStartDate') | ||
262 | replacements.originallyPublishedStartDate = options.originallyPublishedStartDate | ||
263 | } | ||
264 | |||
265 | if (options.originallyPublishedEndDate) { | ||
266 | and.push('"video"."originallyPublishedAt" <= :originallyPublishedEndDate') | ||
267 | replacements.originallyPublishedEndDate = options.originallyPublishedEndDate | ||
268 | } | ||
269 | |||
270 | if (options.durationMin) { | ||
271 | and.push('"video"."duration" >= :durationMin') | ||
272 | replacements.durationMin = options.durationMin | ||
273 | } | ||
274 | |||
275 | if (options.durationMax) { | ||
276 | and.push('"video"."duration" <= :durationMax') | ||
277 | replacements.durationMax = options.durationMax | ||
278 | } | ||
279 | |||
280 | if (options.search) { | ||
281 | const escapedSearch = model.sequelize.escape(options.search) | ||
282 | const escapedLikeSearch = model.sequelize.escape('%' + options.search + '%') | ||
283 | |||
284 | cte.push( | ||
285 | '"trigramSearch" AS (' + | ||
286 | ' SELECT "video"."id", ' + | ||
287 | ` similarity(lower(immutable_unaccent("video"."name")), lower(immutable_unaccent(${escapedSearch}))) as similarity ` + | ||
288 | ' FROM "video" ' + | ||
289 | ' WHERE lower(immutable_unaccent("video"."name")) % lower(immutable_unaccent(' + escapedSearch + ')) OR ' + | ||
290 | ' lower(immutable_unaccent("video"."name")) LIKE lower(immutable_unaccent(' + escapedLikeSearch + '))' + | ||
291 | ')' | ||
292 | ) | ||
293 | |||
294 | joins.push('LEFT JOIN "trigramSearch" ON "video"."id" = "trigramSearch"."id"') | ||
295 | |||
296 | let base = '(' + | ||
297 | ' "trigramSearch"."id" IS NOT NULL OR ' + | ||
298 | ' EXISTS (' + | ||
299 | ' SELECT 1 FROM "videoTag" ' + | ||
300 | ' INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | ||
301 | ` WHERE lower("tag"."name") = ${escapedSearch} ` + | ||
302 | ' AND "video"."id" = "videoTag"."videoId"' + | ||
303 | ' )' | ||
304 | |||
305 | if (validator.isUUID(options.search)) { | ||
306 | base += ` OR "video"."uuid" = ${escapedSearch}` | ||
307 | } | ||
308 | |||
309 | base += ')' | ||
310 | and.push(base) | ||
311 | |||
312 | attributes.push(`COALESCE("trigramSearch"."similarity", 0) as similarity`) | ||
313 | } else { | ||
314 | attributes.push('0 as similarity') | ||
315 | } | ||
316 | |||
317 | if (options.isCount === true) attributes = [ 'COUNT(*) as "total"' ] | ||
318 | |||
319 | let suffix = '' | ||
320 | let order = '' | ||
321 | if (options.isCount !== true) { | ||
322 | |||
323 | if (exists(options.sort)) { | ||
324 | if (options.sort === '-originallyPublishedAt' || options.sort === 'originallyPublishedAt') { | ||
325 | attributes.push('COALESCE("video"."originallyPublishedAt", "video"."publishedAt") AS "publishedAtForOrder"') | ||
326 | } | ||
327 | |||
328 | order = buildOrder(model, options.sort) | ||
329 | suffix += `${order} ` | ||
330 | } | ||
331 | |||
332 | if (exists(options.count)) { | ||
333 | const count = parseInt(options.count + '', 10) | ||
334 | suffix += `LIMIT ${count} ` | ||
335 | } | ||
336 | |||
337 | if (exists(options.start)) { | ||
338 | const start = parseInt(options.start + '', 10) | ||
339 | suffix += `OFFSET ${start} ` | ||
340 | } | ||
341 | } | ||
342 | |||
343 | const cteString = cte.length !== 0 | ||
344 | ? `WITH ${cte.join(', ')} ` | ||
345 | : '' | ||
346 | |||
347 | const query = cteString + | ||
348 | 'SELECT ' + attributes.join(', ') + ' ' + | ||
349 | 'FROM "video" ' + joins.join(' ') + ' ' + | ||
350 | 'WHERE ' + and.join(' AND ') + ' ' + | ||
351 | group + ' ' + | ||
352 | having + ' ' + | ||
353 | suffix | ||
354 | |||
355 | return { query, replacements, order } | ||
356 | } | ||
357 | |||
358 | function buildOrder (model: typeof Model, value: string) { | ||
359 | const { direction, field } = buildDirectionAndField(value) | ||
360 | if (field.match(/^[a-zA-Z."]+$/) === null) throw new Error('Invalid sort column ' + field) | ||
361 | |||
362 | if (field.toLowerCase() === 'random') return 'ORDER BY RANDOM()' | ||
363 | |||
364 | if (field.toLowerCase() === 'trending') { // Sort by aggregation | ||
365 | return `ORDER BY "videoViewsSum" ${direction}, "video"."views" ${direction}` | ||
366 | } | ||
367 | |||
368 | let firstSort: string | ||
369 | |||
370 | if (field.toLowerCase() === 'match') { // Search | ||
371 | firstSort = '"similarity"' | ||
372 | } else if (field === 'originallyPublishedAt') { | ||
373 | firstSort = '"publishedAtForOrder"' | ||
374 | } else if (field.includes('.')) { | ||
375 | firstSort = field | ||
376 | } else { | ||
377 | firstSort = `"video"."${field}"` | ||
378 | } | ||
379 | |||
380 | return `ORDER BY ${firstSort} ${direction}, "video"."id" ASC` | ||
381 | } | ||
382 | |||
383 | function wrapForAPIResults (baseQuery: string, replacements: any, options: BuildVideosQueryOptions, order: string) { | ||
384 | const attributes = { | ||
385 | '"video".*': '', | ||
386 | '"VideoChannel"."id"': '"VideoChannel.id"', | ||
387 | '"VideoChannel"."name"': '"VideoChannel.name"', | ||
388 | '"VideoChannel"."description"': '"VideoChannel.description"', | ||
389 | '"VideoChannel"."actorId"': '"VideoChannel.actorId"', | ||
390 | '"VideoChannel->Actor"."id"': '"VideoChannel.Actor.id"', | ||
391 | '"VideoChannel->Actor"."preferredUsername"': '"VideoChannel.Actor.preferredUsername"', | ||
392 | '"VideoChannel->Actor"."url"': '"VideoChannel.Actor.url"', | ||
393 | '"VideoChannel->Actor"."serverId"': '"VideoChannel.Actor.serverId"', | ||
394 | '"VideoChannel->Actor"."avatarId"': '"VideoChannel.Actor.avatarId"', | ||
395 | '"VideoChannel->Account"."id"': '"VideoChannel.Account.id"', | ||
396 | '"VideoChannel->Account"."name"': '"VideoChannel.Account.name"', | ||
397 | '"VideoChannel->Account->Actor"."id"': '"VideoChannel.Account.Actor.id"', | ||
398 | '"VideoChannel->Account->Actor"."preferredUsername"': '"VideoChannel.Account.Actor.preferredUsername"', | ||
399 | '"VideoChannel->Account->Actor"."url"': '"VideoChannel.Account.Actor.url"', | ||
400 | '"VideoChannel->Account->Actor"."serverId"': '"VideoChannel.Account.Actor.serverId"', | ||
401 | '"VideoChannel->Account->Actor"."avatarId"': '"VideoChannel.Account.Actor.avatarId"', | ||
402 | '"VideoChannel->Actor->Server"."id"': '"VideoChannel.Actor.Server.id"', | ||
403 | '"VideoChannel->Actor->Server"."host"': '"VideoChannel.Actor.Server.host"', | ||
404 | '"VideoChannel->Actor->Avatar"."id"': '"VideoChannel.Actor.Avatar.id"', | ||
405 | '"VideoChannel->Actor->Avatar"."filename"': '"VideoChannel.Actor.Avatar.filename"', | ||
406 | '"VideoChannel->Actor->Avatar"."fileUrl"': '"VideoChannel.Actor.Avatar.fileUrl"', | ||
407 | '"VideoChannel->Actor->Avatar"."onDisk"': '"VideoChannel.Actor.Avatar.onDisk"', | ||
408 | '"VideoChannel->Actor->Avatar"."createdAt"': '"VideoChannel.Actor.Avatar.createdAt"', | ||
409 | '"VideoChannel->Actor->Avatar"."updatedAt"': '"VideoChannel.Actor.Avatar.updatedAt"', | ||
410 | '"VideoChannel->Account->Actor->Server"."id"': '"VideoChannel.Account.Actor.Server.id"', | ||
411 | '"VideoChannel->Account->Actor->Server"."host"': '"VideoChannel.Account.Actor.Server.host"', | ||
412 | '"VideoChannel->Account->Actor->Avatar"."id"': '"VideoChannel.Account.Actor.Avatar.id"', | ||
413 | '"VideoChannel->Account->Actor->Avatar"."filename"': '"VideoChannel.Account.Actor.Avatar.filename"', | ||
414 | '"VideoChannel->Account->Actor->Avatar"."fileUrl"': '"VideoChannel.Account.Actor.Avatar.fileUrl"', | ||
415 | '"VideoChannel->Account->Actor->Avatar"."onDisk"': '"VideoChannel.Account.Actor.Avatar.onDisk"', | ||
416 | '"VideoChannel->Account->Actor->Avatar"."createdAt"': '"VideoChannel.Account.Actor.Avatar.createdAt"', | ||
417 | '"VideoChannel->Account->Actor->Avatar"."updatedAt"': '"VideoChannel.Account.Actor.Avatar.updatedAt"', | ||
418 | '"Thumbnails"."id"': '"Thumbnails.id"', | ||
419 | '"Thumbnails"."type"': '"Thumbnails.type"', | ||
420 | '"Thumbnails"."filename"': '"Thumbnails.filename"' | ||
421 | } | ||
422 | |||
423 | const joins = [ | ||
424 | 'INNER JOIN "video" ON "tmp"."id" = "video"."id"', | ||
425 | |||
426 | 'INNER JOIN "videoChannel" AS "VideoChannel" ON "video"."channelId" = "VideoChannel"."id"', | ||
427 | 'INNER JOIN "actor" AS "VideoChannel->Actor" ON "VideoChannel"."actorId" = "VideoChannel->Actor"."id"', | ||
428 | 'INNER JOIN "account" AS "VideoChannel->Account" ON "VideoChannel"."accountId" = "VideoChannel->Account"."id"', | ||
429 | 'INNER JOIN "actor" AS "VideoChannel->Account->Actor" ON "VideoChannel->Account"."actorId" = "VideoChannel->Account->Actor"."id"', | ||
430 | |||
431 | 'LEFT OUTER JOIN "server" AS "VideoChannel->Actor->Server" ON "VideoChannel->Actor"."serverId" = "VideoChannel->Actor->Server"."id"', | ||
432 | 'LEFT OUTER JOIN "avatar" AS "VideoChannel->Actor->Avatar" ON "VideoChannel->Actor"."avatarId" = "VideoChannel->Actor->Avatar"."id"', | ||
433 | |||
434 | 'LEFT OUTER JOIN "server" AS "VideoChannel->Account->Actor->Server" ' + | ||
435 | 'ON "VideoChannel->Account->Actor"."serverId" = "VideoChannel->Account->Actor->Server"."id"', | ||
436 | |||
437 | 'LEFT OUTER JOIN "avatar" AS "VideoChannel->Account->Actor->Avatar" ' + | ||
438 | 'ON "VideoChannel->Account->Actor"."avatarId" = "VideoChannel->Account->Actor->Avatar"."id"', | ||
439 | |||
440 | 'LEFT OUTER JOIN "thumbnail" AS "Thumbnails" ON "video"."id" = "Thumbnails"."videoId"' | ||
441 | ] | ||
442 | |||
443 | if (options.withFiles) { | ||
444 | joins.push('INNER JOIN "videoFile" AS "VideoFiles" ON "VideoFiles"."videoId" = "video"."id"') | ||
445 | |||
446 | Object.assign(attributes, { | ||
447 | '"VideoFiles"."id"': '"VideoFiles.id"', | ||
448 | '"VideoFiles"."createdAt"': '"VideoFiles.createdAt"', | ||
449 | '"VideoFiles"."updatedAt"': '"VideoFiles.updatedAt"', | ||
450 | '"VideoFiles"."resolution"': '"VideoFiles.resolution"', | ||
451 | '"VideoFiles"."size"': '"VideoFiles.size"', | ||
452 | '"VideoFiles"."extname"': '"VideoFiles.extname"', | ||
453 | '"VideoFiles"."infoHash"': '"VideoFiles.infoHash"', | ||
454 | '"VideoFiles"."fps"': '"VideoFiles.fps"', | ||
455 | '"VideoFiles"."videoId"': '"VideoFiles.videoId"' | ||
456 | }) | ||
457 | } | ||
458 | |||
459 | if (options.user) { | ||
460 | joins.push( | ||
461 | 'LEFT OUTER JOIN "userVideoHistory" ' + | ||
462 | 'ON "video"."id" = "userVideoHistory"."videoId" AND "userVideoHistory"."userId" = :userVideoHistoryId' | ||
463 | ) | ||
464 | replacements.userVideoHistoryId = options.user.id | ||
465 | |||
466 | Object.assign(attributes, { | ||
467 | '"userVideoHistory"."id"': '"userVideoHistory.id"', | ||
468 | '"userVideoHistory"."currentTime"': '"userVideoHistory.currentTime"' | ||
469 | }) | ||
470 | } | ||
471 | |||
472 | if (options.videoPlaylistId) { | ||
473 | joins.push( | ||
474 | 'INNER JOIN "videoPlaylistElement" as "VideoPlaylistElement" ON "videoPlaylistElement"."videoId" = "video"."id" ' + | ||
475 | 'AND "VideoPlaylistElement"."videoPlaylistId" = :videoPlaylistId' | ||
476 | ) | ||
477 | replacements.videoPlaylistId = options.videoPlaylistId | ||
478 | |||
479 | Object.assign(attributes, { | ||
480 | '"VideoPlaylistElement"."createdAt"': '"VideoPlaylistElement.createdAt"', | ||
481 | '"VideoPlaylistElement"."updatedAt"': '"VideoPlaylistElement.updatedAt"', | ||
482 | '"VideoPlaylistElement"."url"': '"VideoPlaylistElement.url"', | ||
483 | '"VideoPlaylistElement"."position"': '"VideoPlaylistElement.position"', | ||
484 | '"VideoPlaylistElement"."startTimestamp"': '"VideoPlaylistElement.startTimestamp"', | ||
485 | '"VideoPlaylistElement"."stopTimestamp"': '"VideoPlaylistElement.stopTimestamp"', | ||
486 | '"VideoPlaylistElement"."videoPlaylistId"': '"VideoPlaylistElement.videoPlaylistId"' | ||
487 | }) | ||
488 | } | ||
489 | |||
490 | const select = 'SELECT ' + Object.keys(attributes).map(key => { | ||
491 | const value = attributes[key] | ||
492 | if (value) return `${key} AS ${value}` | ||
493 | |||
494 | return key | ||
495 | }).join(', ') | ||
496 | |||
497 | return `${select} FROM (${baseQuery}) AS "tmp" ${joins.join(' ')} ${order}` | ||
498 | } | ||
499 | |||
500 | export { | ||
501 | buildListQuery, | ||
502 | wrapForAPIResults | ||
503 | } | ||
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index 50525b4c2..4bbef75e6 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts | |||
@@ -2,12 +2,10 @@ import * as Bluebird from 'bluebird' | |||
2 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 3 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
5 | import { AccountModel } from '../account/account' | ||
6 | import { ActorModel } from '../activitypub/actor' | 5 | import { ActorModel } from '../activitypub/actor' |
7 | import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' | 6 | import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' |
8 | import { VideoModel } from './video' | 7 | import { VideoModel } from './video' |
9 | import { VideoChannelModel } from './video-channel' | 8 | import { literal, Op, Transaction } from 'sequelize' |
10 | import { Op, Transaction } from 'sequelize' | ||
11 | import { MVideoShareActor, MVideoShareFull } from '../../typings/models/video' | 9 | import { MVideoShareActor, MVideoShareFull } from '../../typings/models/video' |
12 | import { MActorDefault } from '../../typings/models' | 10 | import { MActorDefault } from '../../typings/models' |
13 | 11 | ||
@@ -124,70 +122,55 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
124 | } | 122 | } |
125 | 123 | ||
126 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) | 124 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) |
127 | .then((res: MVideoShareFull[]) => res.map(r => r.Actor)) | 125 | .then((res: MVideoShareFull[]) => res.map(r => r.Actor)) |
128 | } | 126 | } |
129 | 127 | ||
130 | static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<MActorDefault[]> { | 128 | static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<MActorDefault[]> { |
129 | const safeOwnerId = parseInt(actorOwnerId + '', 10) | ||
130 | |||
131 | // /!\ On actor model | ||
131 | const query = { | 132 | const query = { |
132 | attributes: [], | 133 | where: { |
133 | include: [ | 134 | [Op.and]: [ |
134 | { | 135 | literal( |
135 | model: ActorModel, | 136 | `EXISTS (` + |
136 | required: true | 137 | ` SELECT 1 FROM "videoShare" ` + |
137 | }, | 138 | ` INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` + |
138 | { | 139 | ` INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ` + |
139 | attributes: [], | 140 | ` INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ` + |
140 | model: VideoModel, | 141 | ` WHERE "videoShare"."actorId" = "ActorModel"."id" AND "account"."actorId" = ${safeOwnerId} ` + |
141 | required: true, | 142 | ` LIMIT 1` + |
142 | include: [ | 143 | `)` |
143 | { | 144 | ) |
144 | attributes: [], | 145 | ] |
145 | model: VideoChannelModel.unscoped(), | 146 | }, |
146 | required: true, | ||
147 | include: [ | ||
148 | { | ||
149 | attributes: [], | ||
150 | model: AccountModel.unscoped(), | ||
151 | required: true, | ||
152 | where: { | ||
153 | actorId: actorOwnerId | ||
154 | } | ||
155 | } | ||
156 | ] | ||
157 | } | ||
158 | ] | ||
159 | } | ||
160 | ], | ||
161 | transaction: t | 147 | transaction: t |
162 | } | 148 | } |
163 | 149 | ||
164 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) | 150 | return ActorModel.findAll(query) |
165 | .then(res => res.map(r => r.Actor)) | ||
166 | } | 151 | } |
167 | 152 | ||
168 | static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<MActorDefault[]> { | 153 | static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<MActorDefault[]> { |
154 | const safeChannelId = parseInt(videoChannelId + '', 10) | ||
155 | |||
156 | // /!\ On actor model | ||
169 | const query = { | 157 | const query = { |
170 | attributes: [], | 158 | where: { |
171 | include: [ | 159 | [Op.and]: [ |
172 | { | 160 | literal( |
173 | model: ActorModel, | 161 | `EXISTS (` + |
174 | required: true | 162 | ` SELECT 1 FROM "videoShare" ` + |
175 | }, | 163 | ` INNER JOIN "video" ON "videoShare"."videoId" = "video"."id" ` + |
176 | { | 164 | ` WHERE "videoShare"."actorId" = "ActorModel"."id" AND "video"."channelId" = ${safeChannelId} ` + |
177 | attributes: [], | 165 | ` LIMIT 1` + |
178 | model: VideoModel, | 166 | `)` |
179 | required: true, | 167 | ) |
180 | where: { | 168 | ] |
181 | channelId: videoChannelId | 169 | }, |
182 | } | ||
183 | } | ||
184 | ], | ||
185 | transaction: t | 170 | transaction: t |
186 | } | 171 | } |
187 | 172 | ||
188 | return VideoShareModel.scope(ScopeNames.FULL) | 173 | return ActorModel.findAll(query) |
189 | .findAll(query) | ||
190 | .then(res => res.map(r => r.Actor)) | ||
191 | } | 174 | } |
192 | 175 | ||
193 | static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction) { | 176 | static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction) { |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index a91a7663d..f5194e259 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -1,18 +1,7 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { maxBy, minBy } from 'lodash' | 2 | import { maxBy, minBy, pick } from 'lodash' |
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { | 4 | import { FindOptions, IncludeOptions, Op, QueryTypes, ScopeOptions, Sequelize, Transaction, WhereOptions } from 'sequelize' |
5 | CountOptions, | ||
6 | FindOptions, | ||
7 | IncludeOptions, | ||
8 | ModelIndexesOptions, | ||
9 | Op, | ||
10 | QueryTypes, | ||
11 | ScopeOptions, | ||
12 | Sequelize, | ||
13 | Transaction, | ||
14 | WhereOptions | ||
15 | } from 'sequelize' | ||
16 | import { | 5 | import { |
17 | AllowNull, | 6 | AllowNull, |
18 | BeforeDestroy, | 7 | BeforeDestroy, |
@@ -54,7 +43,6 @@ import { | |||
54 | } from '../../helpers/custom-validators/videos' | 43 | } from '../../helpers/custom-validators/videos' |
55 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' | 44 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' |
56 | import { logger } from '../../helpers/logger' | 45 | import { logger } from '../../helpers/logger' |
57 | import { getServerActor } from '../../helpers/utils' | ||
58 | import { | 46 | import { |
59 | ACTIVITY_PUB, | 47 | ACTIVITY_PUB, |
60 | API_VERSION, | 48 | API_VERSION, |
@@ -76,16 +64,7 @@ import { AccountVideoRateModel } from '../account/account-video-rate' | |||
76 | import { ActorModel } from '../activitypub/actor' | 64 | import { ActorModel } from '../activitypub/actor' |
77 | import { AvatarModel } from '../avatar/avatar' | 65 | import { AvatarModel } from '../avatar/avatar' |
78 | import { ServerModel } from '../server/server' | 66 | import { ServerModel } from '../server/server' |
79 | import { | 67 | import { buildTrigramSearchIndex, buildWhereIdOrUUID, getVideoSort, isOutdated, throwIfNotValid } from '../utils' |
80 | buildBlockedAccountSQL, | ||
81 | buildTrigramSearchIndex, | ||
82 | buildWhereIdOrUUID, | ||
83 | createSafeIn, | ||
84 | createSimilarityAttribute, | ||
85 | getVideoSort, | ||
86 | isOutdated, | ||
87 | throwIfNotValid | ||
88 | } from '../utils' | ||
89 | import { TagModel } from './tag' | 68 | import { TagModel } from './tag' |
90 | import { VideoAbuseModel } from './video-abuse' | 69 | import { VideoAbuseModel } from './video-abuse' |
91 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | 70 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' |
@@ -132,6 +111,7 @@ import { | |||
132 | MVideoForUser, | 111 | MVideoForUser, |
133 | MVideoFullLight, | 112 | MVideoFullLight, |
134 | MVideoIdThumbnail, | 113 | MVideoIdThumbnail, |
114 | MVideoImmutable, | ||
135 | MVideoThumbnail, | 115 | MVideoThumbnail, |
136 | MVideoThumbnailBlacklist, | 116 | MVideoThumbnailBlacklist, |
137 | MVideoWithAllFiles, | 117 | MVideoWithAllFiles, |
@@ -142,75 +122,10 @@ import { MVideoFile, MVideoFileStreamingPlaylistVideo } from '../../typings/mode | |||
142 | import { MThumbnail } from '../../typings/models/video/thumbnail' | 122 | import { MThumbnail } from '../../typings/models/video/thumbnail' |
143 | import { VideoFile } from '@shared/models/videos/video-file.model' | 123 | import { VideoFile } from '@shared/models/videos/video-file.model' |
144 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' | 124 | import { getHLSDirectory, getTorrentFileName, getTorrentFilePath, getVideoFilename, getVideoFilePath } from '@server/lib/video-paths' |
145 | import validator from 'validator' | 125 | import { ModelCache } from '@server/models/model-cache' |
146 | 126 | import { buildListQuery, BuildVideosQueryOptions, wrapForAPIResults } from './video-query-builder' | |
147 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 127 | import { buildNSFWFilter } from '@server/helpers/express-utils' |
148 | const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ | 128 | import { getServerActor } from '@server/models/application/application' |
149 | buildTrigramSearchIndex('video_name_trigram', 'name'), | ||
150 | |||
151 | { fields: [ 'createdAt' ] }, | ||
152 | { | ||
153 | fields: [ | ||
154 | { name: 'publishedAt', order: 'DESC' }, | ||
155 | { name: 'id', order: 'ASC' } | ||
156 | ] | ||
157 | }, | ||
158 | { fields: [ 'duration' ] }, | ||
159 | { fields: [ 'views' ] }, | ||
160 | { fields: [ 'channelId' ] }, | ||
161 | { | ||
162 | fields: [ 'originallyPublishedAt' ], | ||
163 | where: { | ||
164 | originallyPublishedAt: { | ||
165 | [Op.ne]: null | ||
166 | } | ||
167 | } | ||
168 | }, | ||
169 | { | ||
170 | fields: [ 'category' ], // We don't care videos with an unknown category | ||
171 | where: { | ||
172 | category: { | ||
173 | [Op.ne]: null | ||
174 | } | ||
175 | } | ||
176 | }, | ||
177 | { | ||
178 | fields: [ 'licence' ], // We don't care videos with an unknown licence | ||
179 | where: { | ||
180 | licence: { | ||
181 | [Op.ne]: null | ||
182 | } | ||
183 | } | ||
184 | }, | ||
185 | { | ||
186 | fields: [ 'language' ], // We don't care videos with an unknown language | ||
187 | where: { | ||
188 | language: { | ||
189 | [Op.ne]: null | ||
190 | } | ||
191 | } | ||
192 | }, | ||
193 | { | ||
194 | fields: [ 'nsfw' ], // Most of the videos are not NSFW | ||
195 | where: { | ||
196 | nsfw: true | ||
197 | } | ||
198 | }, | ||
199 | { | ||
200 | fields: [ 'remote' ], // Only index local videos | ||
201 | where: { | ||
202 | remote: false | ||
203 | } | ||
204 | }, | ||
205 | { | ||
206 | fields: [ 'uuid' ], | ||
207 | unique: true | ||
208 | }, | ||
209 | { | ||
210 | fields: [ 'url' ], | ||
211 | unique: true | ||
212 | } | ||
213 | ] | ||
214 | 129 | ||
215 | export enum ScopeNames { | 130 | export enum ScopeNames { |
216 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', | 131 | AVAILABLE_FOR_LIST_IDS = 'AVAILABLE_FOR_LIST_IDS', |
@@ -223,6 +138,7 @@ export enum ScopeNames { | |||
223 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', | 138 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', |
224 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', | 139 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', |
225 | WITH_USER_ID = 'WITH_USER_ID', | 140 | WITH_USER_ID = 'WITH_USER_ID', |
141 | WITH_IMMUTABLE_ATTRIBUTES = 'WITH_IMMUTABLE_ATTRIBUTES', | ||
226 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' | 142 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' |
227 | } | 143 | } |
228 | 144 | ||
@@ -266,7 +182,10 @@ export type AvailableForListIDsOptions = { | |||
266 | } | 182 | } |
267 | 183 | ||
268 | @Scopes(() => ({ | 184 | @Scopes(() => ({ |
269 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { | 185 | [ScopeNames.WITH_IMMUTABLE_ATTRIBUTES]: { |
186 | attributes: [ 'id', 'url', 'uuid', 'remote' ] | ||
187 | }, | ||
188 | [ScopeNames.FOR_API]: (options: ForAPIOptions) => { | ||
270 | const query: FindOptions = { | 189 | const query: FindOptions = { |
271 | include: [ | 190 | include: [ |
272 | { | 191 | { |
@@ -291,14 +210,14 @@ export type AvailableForListIDsOptions = { | |||
291 | if (options.ids) { | 210 | if (options.ids) { |
292 | query.where = { | 211 | query.where = { |
293 | id: { | 212 | id: { |
294 | [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken | 213 | [Op.in]: options.ids |
295 | } | 214 | } |
296 | } | 215 | } |
297 | } | 216 | } |
298 | 217 | ||
299 | if (options.withFiles === true) { | 218 | if (options.withFiles === true) { |
300 | query.include.push({ | 219 | query.include.push({ |
301 | model: VideoFileModel.unscoped(), | 220 | model: VideoFileModel, |
302 | required: true | 221 | required: true |
303 | }) | 222 | }) |
304 | } | 223 | } |
@@ -315,276 +234,7 @@ export type AvailableForListIDsOptions = { | |||
315 | 234 | ||
316 | return query | 235 | return query |
317 | }, | 236 | }, |
318 | [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { | 237 | [ScopeNames.WITH_THUMBNAILS]: { |
319 | const whereAnd = options.baseWhere ? [].concat(options.baseWhere) : [] | ||
320 | |||
321 | const query: FindOptions = { | ||
322 | raw: true, | ||
323 | include: [] | ||
324 | } | ||
325 | |||
326 | const attributesType = options.attributesType || 'id' | ||
327 | |||
328 | if (attributesType === 'id') query.attributes = [ 'id' ] | ||
329 | else if (attributesType === 'none') query.attributes = [ ] | ||
330 | |||
331 | whereAnd.push({ | ||
332 | id: { | ||
333 | [ Op.notIn ]: Sequelize.literal( | ||
334 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' | ||
335 | ) | ||
336 | } | ||
337 | }) | ||
338 | |||
339 | if (options.serverAccountId) { | ||
340 | whereAnd.push({ | ||
341 | channelId: { | ||
342 | [ Op.notIn ]: Sequelize.literal( | ||
343 | '(' + | ||
344 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + | ||
345 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + | ||
346 | ')' + | ||
347 | ')' | ||
348 | ) | ||
349 | } | ||
350 | }) | ||
351 | } | ||
352 | |||
353 | // Only list public/published videos | ||
354 | if (!options.filter || options.filter !== 'all-local') { | ||
355 | |||
356 | const publishWhere = { | ||
357 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding | ||
358 | [ Op.or ]: [ | ||
359 | { | ||
360 | state: VideoState.PUBLISHED | ||
361 | }, | ||
362 | { | ||
363 | [ Op.and ]: { | ||
364 | state: VideoState.TO_TRANSCODE, | ||
365 | waitTranscoding: false | ||
366 | } | ||
367 | } | ||
368 | ] | ||
369 | } | ||
370 | whereAnd.push(publishWhere) | ||
371 | |||
372 | // List internal videos if the user is logged in | ||
373 | if (options.user) { | ||
374 | const privacyWhere = { | ||
375 | [Op.or]: [ | ||
376 | { | ||
377 | privacy: VideoPrivacy.INTERNAL | ||
378 | }, | ||
379 | { | ||
380 | privacy: VideoPrivacy.PUBLIC | ||
381 | } | ||
382 | ] | ||
383 | } | ||
384 | |||
385 | whereAnd.push(privacyWhere) | ||
386 | } else { // Or only public videos | ||
387 | const privacyWhere = { privacy: VideoPrivacy.PUBLIC } | ||
388 | whereAnd.push(privacyWhere) | ||
389 | } | ||
390 | } | ||
391 | |||
392 | if (options.videoPlaylistId) { | ||
393 | query.include.push({ | ||
394 | attributes: [], | ||
395 | model: VideoPlaylistElementModel.unscoped(), | ||
396 | required: true, | ||
397 | where: { | ||
398 | videoPlaylistId: options.videoPlaylistId | ||
399 | } | ||
400 | }) | ||
401 | |||
402 | query.subQuery = false | ||
403 | } | ||
404 | |||
405 | if (options.filter && (options.filter === 'local' || options.filter === 'all-local')) { | ||
406 | whereAnd.push({ | ||
407 | remote: false | ||
408 | }) | ||
409 | } | ||
410 | |||
411 | if (options.accountId || options.videoChannelId) { | ||
412 | const videoChannelInclude: IncludeOptions = { | ||
413 | attributes: [], | ||
414 | model: VideoChannelModel.unscoped(), | ||
415 | required: true | ||
416 | } | ||
417 | |||
418 | if (options.videoChannelId) { | ||
419 | videoChannelInclude.where = { | ||
420 | id: options.videoChannelId | ||
421 | } | ||
422 | } | ||
423 | |||
424 | if (options.accountId) { | ||
425 | const accountInclude: IncludeOptions = { | ||
426 | attributes: [], | ||
427 | model: AccountModel.unscoped(), | ||
428 | required: true | ||
429 | } | ||
430 | |||
431 | accountInclude.where = { id: options.accountId } | ||
432 | videoChannelInclude.include = [ accountInclude ] | ||
433 | } | ||
434 | |||
435 | query.include.push(videoChannelInclude) | ||
436 | } | ||
437 | |||
438 | if (options.followerActorId) { | ||
439 | let localVideosReq = '' | ||
440 | if (options.includeLocalVideos === true) { | ||
441 | localVideosReq = ' UNION ALL SELECT "video"."id" FROM "video" WHERE remote IS FALSE' | ||
442 | } | ||
443 | |||
444 | // Force actorId to be a number to avoid SQL injections | ||
445 | const actorIdNumber = parseInt(options.followerActorId.toString(), 10) | ||
446 | whereAnd.push({ | ||
447 | id: { | ||
448 | [Op.in]: Sequelize.literal( | ||
449 | '(' + | ||
450 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + | ||
451 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | ||
452 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | ||
453 | ' UNION ALL ' + | ||
454 | 'SELECT "video"."id" AS "id" FROM "video" ' + | ||
455 | 'INNER JOIN "videoChannel" ON "videoChannel"."id" = "video"."channelId" ' + | ||
456 | 'INNER JOIN "account" ON "account"."id" = "videoChannel"."accountId" ' + | ||
457 | 'INNER JOIN "actor" ON "account"."actorId" = "actor"."id" ' + | ||
458 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "actor"."id" ' + | ||
459 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | ||
460 | localVideosReq + | ||
461 | ')' | ||
462 | ) | ||
463 | } | ||
464 | }) | ||
465 | } | ||
466 | |||
467 | if (options.withFiles === true) { | ||
468 | whereAnd.push({ | ||
469 | id: { | ||
470 | [ Op.in ]: Sequelize.literal( | ||
471 | '(SELECT "videoId" FROM "videoFile")' | ||
472 | ) | ||
473 | } | ||
474 | }) | ||
475 | } | ||
476 | |||
477 | // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN() | ||
478 | if (options.tagsAllOf || options.tagsOneOf) { | ||
479 | if (options.tagsOneOf) { | ||
480 | const tagsOneOfLower = options.tagsOneOf.map(t => t.toLowerCase()) | ||
481 | |||
482 | whereAnd.push({ | ||
483 | id: { | ||
484 | [ Op.in ]: Sequelize.literal( | ||
485 | '(' + | ||
486 | 'SELECT "videoId" FROM "videoTag" ' + | ||
487 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | ||
488 | 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsOneOfLower) + ')' + | ||
489 | ')' | ||
490 | ) | ||
491 | } | ||
492 | }) | ||
493 | } | ||
494 | |||
495 | if (options.tagsAllOf) { | ||
496 | const tagsAllOfLower = options.tagsAllOf.map(t => t.toLowerCase()) | ||
497 | |||
498 | whereAnd.push({ | ||
499 | id: { | ||
500 | [ Op.in ]: Sequelize.literal( | ||
501 | '(' + | ||
502 | 'SELECT "videoId" FROM "videoTag" ' + | ||
503 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | ||
504 | 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsAllOfLower) + ')' + | ||
505 | 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length + | ||
506 | ')' | ||
507 | ) | ||
508 | } | ||
509 | }) | ||
510 | } | ||
511 | } | ||
512 | |||
513 | if (options.nsfw === true || options.nsfw === false) { | ||
514 | whereAnd.push({ nsfw: options.nsfw }) | ||
515 | } | ||
516 | |||
517 | if (options.categoryOneOf) { | ||
518 | whereAnd.push({ | ||
519 | category: { | ||
520 | [ Op.or ]: options.categoryOneOf | ||
521 | } | ||
522 | }) | ||
523 | } | ||
524 | |||
525 | if (options.licenceOneOf) { | ||
526 | whereAnd.push({ | ||
527 | licence: { | ||
528 | [ Op.or ]: options.licenceOneOf | ||
529 | } | ||
530 | }) | ||
531 | } | ||
532 | |||
533 | if (options.languageOneOf) { | ||
534 | let videoLanguages = options.languageOneOf | ||
535 | if (options.languageOneOf.find(l => l === '_unknown')) { | ||
536 | videoLanguages = videoLanguages.concat([ null ]) | ||
537 | } | ||
538 | |||
539 | whereAnd.push({ | ||
540 | [Op.or]: [ | ||
541 | { | ||
542 | language: { | ||
543 | [ Op.or ]: videoLanguages | ||
544 | } | ||
545 | }, | ||
546 | { | ||
547 | id: { | ||
548 | [ Op.in ]: Sequelize.literal( | ||
549 | '(' + | ||
550 | 'SELECT "videoId" FROM "videoCaption" ' + | ||
551 | 'WHERE "language" IN (' + createSafeIn(VideoModel, options.languageOneOf) + ') ' + | ||
552 | ')' | ||
553 | ) | ||
554 | } | ||
555 | } | ||
556 | ] | ||
557 | }) | ||
558 | } | ||
559 | |||
560 | if (options.trendingDays) { | ||
561 | query.include.push(VideoModel.buildTrendingQuery(options.trendingDays)) | ||
562 | |||
563 | query.subQuery = false | ||
564 | } | ||
565 | |||
566 | if (options.historyOfUser) { | ||
567 | query.include.push({ | ||
568 | model: UserVideoHistoryModel, | ||
569 | required: true, | ||
570 | where: { | ||
571 | userId: options.historyOfUser.id | ||
572 | } | ||
573 | }) | ||
574 | |||
575 | // Even if the relation is n:m, we know that a user only have 0..1 video history | ||
576 | // So we won't have multiple rows for the same video | ||
577 | // Without this, we would not be able to sort on "updatedAt" column of UserVideoHistoryModel | ||
578 | query.subQuery = false | ||
579 | } | ||
580 | |||
581 | query.where = { | ||
582 | [ Op.and ]: whereAnd | ||
583 | } | ||
584 | |||
585 | return query | ||
586 | }, | ||
587 | [ ScopeNames.WITH_THUMBNAILS ]: { | ||
588 | include: [ | 238 | include: [ |
589 | { | 239 | { |
590 | model: ThumbnailModel, | 240 | model: ThumbnailModel, |
@@ -592,7 +242,7 @@ export type AvailableForListIDsOptions = { | |||
592 | } | 242 | } |
593 | ] | 243 | ] |
594 | }, | 244 | }, |
595 | [ ScopeNames.WITH_USER_ID ]: { | 245 | [ScopeNames.WITH_USER_ID]: { |
596 | include: [ | 246 | include: [ |
597 | { | 247 | { |
598 | attributes: [ 'accountId' ], | 248 | attributes: [ 'accountId' ], |
@@ -608,7 +258,7 @@ export type AvailableForListIDsOptions = { | |||
608 | } | 258 | } |
609 | ] | 259 | ] |
610 | }, | 260 | }, |
611 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { | 261 | [ScopeNames.WITH_ACCOUNT_DETAILS]: { |
612 | include: [ | 262 | include: [ |
613 | { | 263 | { |
614 | model: VideoChannelModel.unscoped(), | 264 | model: VideoChannelModel.unscoped(), |
@@ -660,10 +310,10 @@ export type AvailableForListIDsOptions = { | |||
660 | } | 310 | } |
661 | ] | 311 | ] |
662 | }, | 312 | }, |
663 | [ ScopeNames.WITH_TAGS ]: { | 313 | [ScopeNames.WITH_TAGS]: { |
664 | include: [ TagModel ] | 314 | include: [ TagModel ] |
665 | }, | 315 | }, |
666 | [ ScopeNames.WITH_BLACKLISTED ]: { | 316 | [ScopeNames.WITH_BLACKLISTED]: { |
667 | include: [ | 317 | include: [ |
668 | { | 318 | { |
669 | attributes: [ 'id', 'reason', 'unfederated' ], | 319 | attributes: [ 'id', 'reason', 'unfederated' ], |
@@ -672,7 +322,7 @@ export type AvailableForListIDsOptions = { | |||
672 | } | 322 | } |
673 | ] | 323 | ] |
674 | }, | 324 | }, |
675 | [ ScopeNames.WITH_WEBTORRENT_FILES ]: (withRedundancies = false) => { | 325 | [ScopeNames.WITH_WEBTORRENT_FILES]: (withRedundancies = false) => { |
676 | let subInclude: any[] = [] | 326 | let subInclude: any[] = [] |
677 | 327 | ||
678 | if (withRedundancies === true) { | 328 | if (withRedundancies === true) { |
@@ -688,7 +338,7 @@ export type AvailableForListIDsOptions = { | |||
688 | return { | 338 | return { |
689 | include: [ | 339 | include: [ |
690 | { | 340 | { |
691 | model: VideoFileModel.unscoped(), | 341 | model: VideoFileModel, |
692 | separate: true, // We may have multiple files, having multiple redundancies so let's separate this join | 342 | separate: true, // We may have multiple files, having multiple redundancies so let's separate this join |
693 | required: false, | 343 | required: false, |
694 | include: subInclude | 344 | include: subInclude |
@@ -696,10 +346,10 @@ export type AvailableForListIDsOptions = { | |||
696 | ] | 346 | ] |
697 | } | 347 | } |
698 | }, | 348 | }, |
699 | [ ScopeNames.WITH_STREAMING_PLAYLISTS ]: (withRedundancies = false) => { | 349 | [ScopeNames.WITH_STREAMING_PLAYLISTS]: (withRedundancies = false) => { |
700 | const subInclude: IncludeOptions[] = [ | 350 | const subInclude: IncludeOptions[] = [ |
701 | { | 351 | { |
702 | model: VideoFileModel.unscoped(), | 352 | model: VideoFileModel, |
703 | required: false | 353 | required: false |
704 | } | 354 | } |
705 | ] | 355 | ] |
@@ -723,7 +373,7 @@ export type AvailableForListIDsOptions = { | |||
723 | ] | 373 | ] |
724 | } | 374 | } |
725 | }, | 375 | }, |
726 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { | 376 | [ScopeNames.WITH_SCHEDULED_UPDATE]: { |
727 | include: [ | 377 | include: [ |
728 | { | 378 | { |
729 | model: ScheduleVideoUpdateModel.unscoped(), | 379 | model: ScheduleVideoUpdateModel.unscoped(), |
@@ -731,7 +381,7 @@ export type AvailableForListIDsOptions = { | |||
731 | } | 381 | } |
732 | ] | 382 | ] |
733 | }, | 383 | }, |
734 | [ ScopeNames.WITH_USER_HISTORY ]: (userId: number) => { | 384 | [ScopeNames.WITH_USER_HISTORY]: (userId: number) => { |
735 | return { | 385 | return { |
736 | include: [ | 386 | include: [ |
737 | { | 387 | { |
@@ -748,7 +398,72 @@ export type AvailableForListIDsOptions = { | |||
748 | })) | 398 | })) |
749 | @Table({ | 399 | @Table({ |
750 | tableName: 'video', | 400 | tableName: 'video', |
751 | indexes | 401 | indexes: [ |
402 | buildTrigramSearchIndex('video_name_trigram', 'name'), | ||
403 | |||
404 | { fields: [ 'createdAt' ] }, | ||
405 | { | ||
406 | fields: [ | ||
407 | { name: 'publishedAt', order: 'DESC' }, | ||
408 | { name: 'id', order: 'ASC' } | ||
409 | ] | ||
410 | }, | ||
411 | { fields: [ 'duration' ] }, | ||
412 | { fields: [ 'views' ] }, | ||
413 | { fields: [ 'channelId' ] }, | ||
414 | { | ||
415 | fields: [ 'originallyPublishedAt' ], | ||
416 | where: { | ||
417 | originallyPublishedAt: { | ||
418 | [Op.ne]: null | ||
419 | } | ||
420 | } | ||
421 | }, | ||
422 | { | ||
423 | fields: [ 'category' ], // We don't care videos with an unknown category | ||
424 | where: { | ||
425 | category: { | ||
426 | [Op.ne]: null | ||
427 | } | ||
428 | } | ||
429 | }, | ||
430 | { | ||
431 | fields: [ 'licence' ], // We don't care videos with an unknown licence | ||
432 | where: { | ||
433 | licence: { | ||
434 | [Op.ne]: null | ||
435 | } | ||
436 | } | ||
437 | }, | ||
438 | { | ||
439 | fields: [ 'language' ], // We don't care videos with an unknown language | ||
440 | where: { | ||
441 | language: { | ||
442 | [Op.ne]: null | ||
443 | } | ||
444 | } | ||
445 | }, | ||
446 | { | ||
447 | fields: [ 'nsfw' ], // Most of the videos are not NSFW | ||
448 | where: { | ||
449 | nsfw: true | ||
450 | } | ||
451 | }, | ||
452 | { | ||
453 | fields: [ 'remote' ], // Only index local videos | ||
454 | where: { | ||
455 | remote: false | ||
456 | } | ||
457 | }, | ||
458 | { | ||
459 | fields: [ 'uuid' ], | ||
460 | unique: true | ||
461 | }, | ||
462 | { | ||
463 | fields: [ 'url' ], | ||
464 | unique: true | ||
465 | } | ||
466 | ] | ||
752 | }) | 467 | }) |
753 | export class VideoModel extends Model<VideoModel> { | 468 | export class VideoModel extends Model<VideoModel> { |
754 | 469 | ||
@@ -913,9 +628,9 @@ export class VideoModel extends Model<VideoModel> { | |||
913 | @HasMany(() => VideoAbuseModel, { | 628 | @HasMany(() => VideoAbuseModel, { |
914 | foreignKey: { | 629 | foreignKey: { |
915 | name: 'videoId', | 630 | name: 'videoId', |
916 | allowNull: false | 631 | allowNull: true |
917 | }, | 632 | }, |
918 | onDelete: 'cascade' | 633 | onDelete: 'set null' |
919 | }) | 634 | }) |
920 | VideoAbuses: VideoAbuseModel[] | 635 | VideoAbuses: VideoAbuseModel[] |
921 | 636 | ||
@@ -1019,7 +734,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1019 | }, | 734 | }, |
1020 | onDelete: 'cascade', | 735 | onDelete: 'cascade', |
1021 | hooks: true, | 736 | hooks: true, |
1022 | [ 'separate' as any ]: true | 737 | ['separate' as any]: true |
1023 | }) | 738 | }) |
1024 | VideoCaptions: VideoCaptionModel[] | 739 | VideoCaptions: VideoCaptionModel[] |
1025 | 740 | ||
@@ -1078,6 +793,38 @@ export class VideoModel extends Model<VideoModel> { | |||
1078 | return undefined | 793 | return undefined |
1079 | } | 794 | } |
1080 | 795 | ||
796 | @BeforeDestroy | ||
797 | static invalidateCache (instance: VideoModel) { | ||
798 | ModelCache.Instance.invalidateCache('video', instance.id) | ||
799 | } | ||
800 | |||
801 | @BeforeDestroy | ||
802 | static async saveEssentialDataToAbuses (instance: VideoModel, options) { | ||
803 | const tasks: Promise<any>[] = [] | ||
804 | |||
805 | logger.info('Saving video abuses details of video %s.', instance.url) | ||
806 | |||
807 | if (!Array.isArray(instance.VideoAbuses)) { | ||
808 | instance.VideoAbuses = await instance.$get('VideoAbuses') | ||
809 | |||
810 | if (instance.VideoAbuses.length === 0) return undefined | ||
811 | } | ||
812 | |||
813 | const details = instance.toFormattedDetailsJSON() | ||
814 | |||
815 | for (const abuse of instance.VideoAbuses) { | ||
816 | abuse.deletedVideo = details | ||
817 | tasks.push(abuse.save({ transaction: options.transaction })) | ||
818 | } | ||
819 | |||
820 | Promise.all(tasks) | ||
821 | .catch(err => { | ||
822 | logger.error('Some errors when saving details of video %s in its abuses before destroy hook.', instance.uuid, { err }) | ||
823 | }) | ||
824 | |||
825 | return undefined | ||
826 | } | ||
827 | |||
1081 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { | 828 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { |
1082 | const query = { | 829 | const query = { |
1083 | where: { | 830 | where: { |
@@ -1112,19 +859,19 @@ export class VideoModel extends Model<VideoModel> { | |||
1112 | distinct: true, | 859 | distinct: true, |
1113 | offset: start, | 860 | offset: start, |
1114 | limit: count, | 861 | limit: count, |
1115 | order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings | 862 | order: getVideoSort('-createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings |
1116 | where: { | 863 | where: { |
1117 | id: { | 864 | id: { |
1118 | [ Op.in ]: Sequelize.literal('(' + rawQuery + ')') | 865 | [Op.in]: Sequelize.literal('(' + rawQuery + ')') |
1119 | }, | 866 | }, |
1120 | [ Op.or ]: [ | 867 | [Op.or]: [ |
1121 | { privacy: VideoPrivacy.PUBLIC }, | 868 | { privacy: VideoPrivacy.PUBLIC }, |
1122 | { privacy: VideoPrivacy.UNLISTED } | 869 | { privacy: VideoPrivacy.UNLISTED } |
1123 | ] | 870 | ] |
1124 | }, | 871 | }, |
1125 | include: [ | 872 | include: [ |
1126 | { | 873 | { |
1127 | attributes: [ 'language' ], | 874 | attributes: [ 'language', 'fileUrl' ], |
1128 | model: VideoCaptionModel.unscoped(), | 875 | model: VideoCaptionModel.unscoped(), |
1129 | required: false | 876 | required: false |
1130 | }, | 877 | }, |
@@ -1134,10 +881,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1134 | required: false, | 881 | required: false, |
1135 | // We only want videos shared by this actor | 882 | // We only want videos shared by this actor |
1136 | where: { | 883 | where: { |
1137 | [ Op.and ]: [ | 884 | [Op.and]: [ |
1138 | { | 885 | { |
1139 | id: { | 886 | id: { |
1140 | [ Op.not ]: null | 887 | [Op.not]: null |
1141 | } | 888 | } |
1142 | }, | 889 | }, |
1143 | { | 890 | { |
@@ -1187,8 +934,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1187 | // totals: totalVideos + totalVideoShares | 934 | // totals: totalVideos + totalVideoShares |
1188 | let totalVideos = 0 | 935 | let totalVideos = 0 |
1189 | let totalVideoShares = 0 | 936 | let totalVideoShares = 0 |
1190 | if (totals[ 0 ]) totalVideos = parseInt(totals[ 0 ].total, 10) | 937 | if (totals[0]) totalVideos = parseInt(totals[0].total, 10) |
1191 | if (totals[ 1 ]) totalVideoShares = parseInt(totals[ 1 ].total, 10) | 938 | if (totals[1]) totalVideoShares = parseInt(totals[1].total, 10) |
1192 | 939 | ||
1193 | const total = totalVideos + totalVideoShares | 940 | const total = totalVideos + totalVideoShares |
1194 | return { | 941 | return { |
@@ -1231,7 +978,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1231 | baseQuery = Object.assign(baseQuery, { | 978 | baseQuery = Object.assign(baseQuery, { |
1232 | where: { | 979 | where: { |
1233 | name: { | 980 | name: { |
1234 | [ Op.iLike ]: '%' + search + '%' | 981 | [Op.iLike]: '%' + search + '%' |
1235 | } | 982 | } |
1236 | } | 983 | } |
1237 | }) | 984 | }) |
@@ -1261,50 +1008,46 @@ export class VideoModel extends Model<VideoModel> { | |||
1261 | } | 1008 | } |
1262 | 1009 | ||
1263 | static async listForApi (options: { | 1010 | static async listForApi (options: { |
1264 | start: number, | 1011 | start: number |
1265 | count: number, | 1012 | count: number |
1266 | sort: string, | 1013 | sort: string |
1267 | nsfw: boolean, | 1014 | nsfw: boolean |
1268 | includeLocalVideos: boolean, | 1015 | includeLocalVideos: boolean |
1269 | withFiles: boolean, | 1016 | withFiles: boolean |
1270 | categoryOneOf?: number[], | 1017 | categoryOneOf?: number[] |
1271 | licenceOneOf?: number[], | 1018 | licenceOneOf?: number[] |
1272 | languageOneOf?: string[], | 1019 | languageOneOf?: string[] |
1273 | tagsOneOf?: string[], | 1020 | tagsOneOf?: string[] |
1274 | tagsAllOf?: string[], | 1021 | tagsAllOf?: string[] |
1275 | filter?: VideoFilter, | 1022 | filter?: VideoFilter |
1276 | accountId?: number, | 1023 | accountId?: number |
1277 | videoChannelId?: number, | 1024 | videoChannelId?: number |
1278 | followerActorId?: number | 1025 | followerActorId?: number |
1279 | videoPlaylistId?: number, | 1026 | videoPlaylistId?: number |
1280 | trendingDays?: number, | 1027 | trendingDays?: number |
1281 | user?: MUserAccountId, | 1028 | user?: MUserAccountId |
1282 | historyOfUser?: MUserId, | 1029 | historyOfUser?: MUserId |
1283 | countVideos?: boolean | 1030 | countVideos?: boolean |
1284 | }) { | 1031 | }) { |
1285 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { | 1032 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { |
1286 | throw new Error('Try to filter all-local but no user has not the see all videos right') | 1033 | throw new Error('Try to filter all-local but no user has not the see all videos right') |
1287 | } | 1034 | } |
1288 | 1035 | ||
1289 | const query: FindOptions & { where?: null } = { | 1036 | const trendingDays = options.sort.endsWith('trending') |
1290 | offset: options.start, | 1037 | ? CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS |
1291 | limit: options.count, | 1038 | : undefined |
1292 | order: getVideoSort(options.sort) | ||
1293 | } | ||
1294 | |||
1295 | let trendingDays: number | ||
1296 | if (options.sort.endsWith('trending')) { | ||
1297 | trendingDays = CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS | ||
1298 | |||
1299 | query.group = 'VideoModel.id' | ||
1300 | } | ||
1301 | 1039 | ||
1302 | const serverActor = await getServerActor() | 1040 | const serverActor = await getServerActor() |
1303 | 1041 | ||
1304 | // followerActorId === null has a meaning, so just check undefined | 1042 | // followerActorId === null has a meaning, so just check undefined |
1305 | const followerActorId = options.followerActorId !== undefined ? options.followerActorId : serverActor.id | 1043 | const followerActorId = options.followerActorId !== undefined |
1044 | ? options.followerActorId | ||
1045 | : serverActor.id | ||
1306 | 1046 | ||
1307 | const queryOptions = { | 1047 | const queryOptions = { |
1048 | start: options.start, | ||
1049 | count: options.count, | ||
1050 | sort: options.sort, | ||
1308 | followerActorId, | 1051 | followerActorId, |
1309 | serverAccountId: serverActor.Account.id, | 1052 | serverAccountId: serverActor.Account.id, |
1310 | nsfw: options.nsfw, | 1053 | nsfw: options.nsfw, |
@@ -1324,7 +1067,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1324 | trendingDays | 1067 | trendingDays |
1325 | } | 1068 | } |
1326 | 1069 | ||
1327 | return VideoModel.getAvailableForApi(query, queryOptions, options.countVideos) | 1070 | return VideoModel.getAvailableForApi(queryOptions, options.countVideos) |
1328 | } | 1071 | } |
1329 | 1072 | ||
1330 | static async searchAndPopulateAccountAndServer (options: { | 1073 | static async searchAndPopulateAccountAndServer (options: { |
@@ -1345,91 +1088,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1345 | tagsAllOf?: string[] | 1088 | tagsAllOf?: string[] |
1346 | durationMin?: number // seconds | 1089 | durationMin?: number // seconds |
1347 | durationMax?: number // seconds | 1090 | durationMax?: number // seconds |
1348 | user?: MUserAccountId, | 1091 | user?: MUserAccountId |
1349 | filter?: VideoFilter | 1092 | filter?: VideoFilter |
1350 | }) { | 1093 | }) { |
1351 | const whereAnd = [] | ||
1352 | |||
1353 | if (options.startDate || options.endDate) { | ||
1354 | const publishedAtRange = {} | ||
1355 | |||
1356 | if (options.startDate) publishedAtRange[ Op.gte ] = options.startDate | ||
1357 | if (options.endDate) publishedAtRange[ Op.lte ] = options.endDate | ||
1358 | |||
1359 | whereAnd.push({ publishedAt: publishedAtRange }) | ||
1360 | } | ||
1361 | |||
1362 | if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { | ||
1363 | const originallyPublishedAtRange = {} | ||
1364 | |||
1365 | if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Op.gte ] = options.originallyPublishedStartDate | ||
1366 | if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Op.lte ] = options.originallyPublishedEndDate | ||
1367 | |||
1368 | whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) | ||
1369 | } | ||
1370 | |||
1371 | if (options.durationMin || options.durationMax) { | ||
1372 | const durationRange = {} | ||
1373 | |||
1374 | if (options.durationMin) durationRange[ Op.gte ] = options.durationMin | ||
1375 | if (options.durationMax) durationRange[ Op.lte ] = options.durationMax | ||
1376 | |||
1377 | whereAnd.push({ duration: durationRange }) | ||
1378 | } | ||
1379 | |||
1380 | const attributesInclude = [] | ||
1381 | const escapedSearch = VideoModel.sequelize.escape(options.search) | ||
1382 | const escapedLikeSearch = VideoModel.sequelize.escape('%' + options.search + '%') | ||
1383 | if (options.search) { | ||
1384 | const trigramSearch = { | ||
1385 | id: { | ||
1386 | [ Op.in ]: Sequelize.literal( | ||
1387 | '(' + | ||
1388 | 'SELECT "video"."id" FROM "video" ' + | ||
1389 | 'WHERE ' + | ||
1390 | 'lower(immutable_unaccent("video"."name")) % lower(immutable_unaccent(' + escapedSearch + ')) OR ' + | ||
1391 | 'lower(immutable_unaccent("video"."name")) LIKE lower(immutable_unaccent(' + escapedLikeSearch + '))' + | ||
1392 | 'UNION ALL ' + | ||
1393 | 'SELECT "video"."id" FROM "video" LEFT JOIN "videoTag" ON "videoTag"."videoId" = "video"."id" ' + | ||
1394 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | ||
1395 | 'WHERE lower("tag"."name") = lower(' + escapedSearch + ')' + | ||
1396 | ')' | ||
1397 | ) | ||
1398 | } | ||
1399 | } | ||
1400 | |||
1401 | if (validator.isUUID(options.search)) { | ||
1402 | whereAnd.push({ | ||
1403 | [Op.or]: [ | ||
1404 | trigramSearch, | ||
1405 | { | ||
1406 | uuid: options.search | ||
1407 | } | ||
1408 | ] | ||
1409 | }) | ||
1410 | } else { | ||
1411 | whereAnd.push(trigramSearch) | ||
1412 | } | ||
1413 | |||
1414 | attributesInclude.push(createSimilarityAttribute('VideoModel.name', options.search)) | ||
1415 | } | ||
1416 | |||
1417 | // Cannot search on similarity if we don't have a search | ||
1418 | if (!options.search) { | ||
1419 | attributesInclude.push( | ||
1420 | Sequelize.literal('0 as similarity') | ||
1421 | ) | ||
1422 | } | ||
1423 | |||
1424 | const query = { | ||
1425 | attributes: { | ||
1426 | include: attributesInclude | ||
1427 | }, | ||
1428 | offset: options.start, | ||
1429 | limit: options.count, | ||
1430 | order: getVideoSort(options.sort) | ||
1431 | } | ||
1432 | |||
1433 | const serverActor = await getServerActor() | 1094 | const serverActor = await getServerActor() |
1434 | const queryOptions = { | 1095 | const queryOptions = { |
1435 | followerActorId: serverActor.id, | 1096 | followerActorId: serverActor.id, |
@@ -1443,10 +1104,21 @@ export class VideoModel extends Model<VideoModel> { | |||
1443 | tagsAllOf: options.tagsAllOf, | 1104 | tagsAllOf: options.tagsAllOf, |
1444 | user: options.user, | 1105 | user: options.user, |
1445 | filter: options.filter, | 1106 | filter: options.filter, |
1446 | baseWhere: whereAnd | 1107 | start: options.start, |
1108 | count: options.count, | ||
1109 | sort: options.sort, | ||
1110 | startDate: options.startDate, | ||
1111 | endDate: options.endDate, | ||
1112 | originallyPublishedStartDate: options.originallyPublishedStartDate, | ||
1113 | originallyPublishedEndDate: options.originallyPublishedEndDate, | ||
1114 | |||
1115 | durationMin: options.durationMin, | ||
1116 | durationMax: options.durationMax, | ||
1117 | |||
1118 | search: options.search | ||
1447 | } | 1119 | } |
1448 | 1120 | ||
1449 | return VideoModel.getAvailableForApi(query, queryOptions) | 1121 | return VideoModel.getAvailableForApi(queryOptions) |
1450 | } | 1122 | } |
1451 | 1123 | ||
1452 | static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> { | 1124 | static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> { |
@@ -1472,6 +1144,24 @@ export class VideoModel extends Model<VideoModel> { | |||
1472 | ]).findOne(options) | 1144 | ]).findOne(options) |
1473 | } | 1145 | } |
1474 | 1146 | ||
1147 | static loadImmutableAttributes (id: number | string, t?: Transaction): Bluebird<MVideoImmutable> { | ||
1148 | const fun = () => { | ||
1149 | const query = { | ||
1150 | where: buildWhereIdOrUUID(id), | ||
1151 | transaction: t | ||
1152 | } | ||
1153 | |||
1154 | return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query) | ||
1155 | } | ||
1156 | |||
1157 | return ModelCache.Instance.doCache({ | ||
1158 | cacheType: 'load-video-immutable-id', | ||
1159 | key: '' + id, | ||
1160 | deleteKey: 'video', | ||
1161 | fun | ||
1162 | }) | ||
1163 | } | ||
1164 | |||
1475 | static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { | 1165 | static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { |
1476 | const where = buildWhereIdOrUUID(id) | 1166 | const where = buildWhereIdOrUUID(id) |
1477 | const options = { | 1167 | const options = { |
@@ -1535,6 +1225,26 @@ export class VideoModel extends Model<VideoModel> { | |||
1535 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) | 1225 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) |
1536 | } | 1226 | } |
1537 | 1227 | ||
1228 | static loadByUrlImmutableAttributes (url: string, transaction?: Transaction): Bluebird<MVideoImmutable> { | ||
1229 | const fun = () => { | ||
1230 | const query: FindOptions = { | ||
1231 | where: { | ||
1232 | url | ||
1233 | }, | ||
1234 | transaction | ||
1235 | } | ||
1236 | |||
1237 | return VideoModel.scope(ScopeNames.WITH_IMMUTABLE_ATTRIBUTES).findOne(query) | ||
1238 | } | ||
1239 | |||
1240 | return ModelCache.Instance.doCache({ | ||
1241 | cacheType: 'load-video-immutable-url', | ||
1242 | key: url, | ||
1243 | deleteKey: 'video', | ||
1244 | fun | ||
1245 | }) | ||
1246 | } | ||
1247 | |||
1538 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { | 1248 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { |
1539 | const query: FindOptions = { | 1249 | const query: FindOptions = { |
1540 | where: { | 1250 | where: { |
@@ -1581,8 +1291,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1581 | } | 1291 | } |
1582 | 1292 | ||
1583 | static loadForGetAPI (parameters: { | 1293 | static loadForGetAPI (parameters: { |
1584 | id: number | string, | 1294 | id: number | string |
1585 | t?: Transaction, | 1295 | t?: Transaction |
1586 | userId?: number | 1296 | userId?: number |
1587 | }): Bluebird<MVideoDetails> { | 1297 | }): Bluebird<MVideoDetails> { |
1588 | const { id, t, userId } = parameters | 1298 | const { id, t, userId } = parameters |
@@ -1619,16 +1329,25 @@ export class VideoModel extends Model<VideoModel> { | |||
1619 | remote: false | 1329 | remote: false |
1620 | } | 1330 | } |
1621 | }) | 1331 | }) |
1622 | const totalVideos = await VideoModel.count() | ||
1623 | 1332 | ||
1624 | let totalLocalVideoViews = await VideoModel.sum('views', { | 1333 | let totalLocalVideoViews = await VideoModel.sum('views', { |
1625 | where: { | 1334 | where: { |
1626 | remote: false | 1335 | remote: false |
1627 | } | 1336 | } |
1628 | }) | 1337 | }) |
1338 | |||
1629 | // Sequelize could return null... | 1339 | // Sequelize could return null... |
1630 | if (!totalLocalVideoViews) totalLocalVideoViews = 0 | 1340 | if (!totalLocalVideoViews) totalLocalVideoViews = 0 |
1631 | 1341 | ||
1342 | const { total: totalVideos } = await VideoModel.listForApi({ | ||
1343 | start: 0, | ||
1344 | count: 0, | ||
1345 | sort: '-publishedAt', | ||
1346 | nsfw: buildNSFWFilter(), | ||
1347 | includeLocalVideos: true, | ||
1348 | withFiles: false | ||
1349 | }) | ||
1350 | |||
1632 | return { | 1351 | return { |
1633 | totalLocalVideos, | 1352 | totalLocalVideos, |
1634 | totalLocalVideoViews, | 1353 | totalLocalVideoViews, |
@@ -1648,9 +1367,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1648 | static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { | 1367 | static checkVideoHasInstanceFollow (videoId: number, followerActorId: number) { |
1649 | // Instances only share videos | 1368 | // Instances only share videos |
1650 | const query = 'SELECT 1 FROM "videoShare" ' + | 1369 | const query = 'SELECT 1 FROM "videoShare" ' + |
1651 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | 1370 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + |
1652 | 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + | 1371 | 'WHERE "actorFollow"."actorId" = $followerActorId AND "videoShare"."videoId" = $videoId ' + |
1653 | 'LIMIT 1' | 1372 | 'LIMIT 1' |
1654 | 1373 | ||
1655 | const options = { | 1374 | const options = { |
1656 | type: QueryTypes.SELECT as QueryTypes.SELECT, | 1375 | type: QueryTypes.SELECT as QueryTypes.SELECT, |
@@ -1682,7 +1401,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1682 | } | 1401 | } |
1683 | 1402 | ||
1684 | return VideoModel.findAll(query) | 1403 | return VideoModel.findAll(query) |
1685 | .then(videos => videos.map(v => v.id)) | 1404 | .then(videos => videos.map(v => v.id)) |
1686 | } | 1405 | } |
1687 | 1406 | ||
1688 | // threshold corresponds to how many video the field should have to be returned | 1407 | // threshold corresponds to how many video the field should have to be returned |
@@ -1690,26 +1409,22 @@ export class VideoModel extends Model<VideoModel> { | |||
1690 | const serverActor = await getServerActor() | 1409 | const serverActor = await getServerActor() |
1691 | const followerActorId = serverActor.id | 1410 | const followerActorId = serverActor.id |
1692 | 1411 | ||
1693 | const scopeOptions: AvailableForListIDsOptions = { | 1412 | const queryOptions: BuildVideosQueryOptions = { |
1413 | attributes: [ `"${field}"` ], | ||
1414 | group: `GROUP BY "${field}"`, | ||
1415 | having: `HAVING COUNT("${field}") >= ${threshold}`, | ||
1416 | start: 0, | ||
1417 | sort: 'random', | ||
1418 | count, | ||
1694 | serverAccountId: serverActor.Account.id, | 1419 | serverAccountId: serverActor.Account.id, |
1695 | followerActorId, | 1420 | followerActorId, |
1696 | includeLocalVideos: true, | 1421 | includeLocalVideos: true |
1697 | attributesType: 'none' // Don't break aggregation | ||
1698 | } | 1422 | } |
1699 | 1423 | ||
1700 | const query: FindOptions = { | 1424 | const { query, replacements } = buildListQuery(VideoModel, queryOptions) |
1701 | attributes: [ field ], | ||
1702 | limit: count, | ||
1703 | group: field, | ||
1704 | having: Sequelize.where( | ||
1705 | Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } | ||
1706 | ), | ||
1707 | order: [ (this.sequelize as any).random() ] | ||
1708 | } | ||
1709 | 1425 | ||
1710 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) | 1426 | return this.sequelize.query<any>(query, { replacements, type: QueryTypes.SELECT }) |
1711 | .findAll(query) | 1427 | .then(rows => rows.map(r => r[field])) |
1712 | .then(rows => rows.map(r => r[ field ])) | ||
1713 | } | 1428 | } |
1714 | 1429 | ||
1715 | static buildTrendingQuery (trendingDays: number) { | 1430 | static buildTrendingQuery (trendingDays: number) { |
@@ -1720,42 +1435,37 @@ export class VideoModel extends Model<VideoModel> { | |||
1720 | required: false, | 1435 | required: false, |
1721 | where: { | 1436 | where: { |
1722 | startDate: { | 1437 | startDate: { |
1723 | [ Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) | 1438 | [Op.gte]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) |
1724 | } | 1439 | } |
1725 | } | 1440 | } |
1726 | } | 1441 | } |
1727 | } | 1442 | } |
1728 | 1443 | ||
1729 | private static async getAvailableForApi ( | 1444 | private static async getAvailableForApi ( |
1730 | query: FindOptions & { where?: null }, // Forbid where field in query | 1445 | options: BuildVideosQueryOptions, |
1731 | options: AvailableForListIDsOptions, | ||
1732 | countVideos = true | 1446 | countVideos = true |
1733 | ) { | 1447 | ) { |
1734 | const idsScope: ScopeOptions = { | 1448 | function getCount () { |
1735 | method: [ | 1449 | if (countVideos !== true) return Promise.resolve(undefined) |
1736 | ScopeNames.AVAILABLE_FOR_LIST_IDS, options | ||
1737 | ] | ||
1738 | } | ||
1739 | 1450 | ||
1740 | // Remove trending sort on count, because it uses a group by | 1451 | const countOptions = Object.assign({}, options, { isCount: true }) |
1741 | const countOptions = Object.assign({}, options, { trendingDays: undefined }) | 1452 | const { query: queryCount, replacements: replacementsCount } = buildListQuery(VideoModel, countOptions) |
1742 | const countQuery: CountOptions = Object.assign({}, query, { attributes: undefined, group: undefined }) | 1453 | |
1743 | const countScope: ScopeOptions = { | 1454 | return VideoModel.sequelize.query<any>(queryCount, { replacements: replacementsCount, type: QueryTypes.SELECT }) |
1744 | method: [ | 1455 | .then(rows => rows.length !== 0 ? rows[0].total : 0) |
1745 | ScopeNames.AVAILABLE_FOR_LIST_IDS, countOptions | ||
1746 | ] | ||
1747 | } | 1456 | } |
1748 | 1457 | ||
1749 | const [ count, rows ] = await Promise.all([ | 1458 | function getModels () { |
1750 | countVideos | 1459 | if (options.count === 0) return Promise.resolve([]) |
1751 | ? VideoModel.scope(countScope).count(countQuery) | 1460 | |
1752 | : Promise.resolve<number>(undefined), | 1461 | const { query, replacements, order } = buildListQuery(VideoModel, options) |
1462 | const queryModels = wrapForAPIResults(query, replacements, options, order) | ||
1753 | 1463 | ||
1754 | VideoModel.scope(idsScope) | 1464 | return VideoModel.sequelize.query<any>(queryModels, { replacements, type: QueryTypes.SELECT, nest: true }) |
1755 | .findAll(Object.assign({}, query, { raw: true })) | 1465 | .then(rows => VideoModel.buildAPIResult(rows)) |
1756 | .then(rows => rows.map(r => r.id)) | 1466 | } |
1757 | .then(ids => VideoModel.loadCompleteVideosForApi(ids, query, options)) | 1467 | |
1758 | ]) | 1468 | const [ count, rows ] = await Promise.all([ getCount(), getModels() ]) |
1759 | 1469 | ||
1760 | return { | 1470 | return { |
1761 | data: rows, | 1471 | data: rows, |
@@ -1763,37 +1473,113 @@ export class VideoModel extends Model<VideoModel> { | |||
1763 | } | 1473 | } |
1764 | } | 1474 | } |
1765 | 1475 | ||
1766 | private static loadCompleteVideosForApi (ids: number[], query: FindOptions, options: AvailableForListIDsOptions) { | 1476 | private static buildAPIResult (rows: any[]) { |
1767 | if (ids.length === 0) return [] | 1477 | const memo: { [ id: number ]: VideoModel } = {} |
1478 | |||
1479 | const thumbnailsDone = new Set<number>() | ||
1480 | const historyDone = new Set<number>() | ||
1481 | const videoFilesDone = new Set<number>() | ||
1482 | |||
1483 | const videos: VideoModel[] = [] | ||
1484 | |||
1485 | const avatarKeys = [ 'id', 'filename', 'fileUrl', 'onDisk', 'createdAt', 'updatedAt' ] | ||
1486 | const actorKeys = [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ] | ||
1487 | const serverKeys = [ 'id', 'host' ] | ||
1488 | const videoFileKeys = [ 'id', 'createdAt', 'updatedAt', 'resolution', 'size', 'extname', 'infoHash', 'fps', 'videoId' ] | ||
1489 | const videoKeys = [ | ||
1490 | 'id', | ||
1491 | 'uuid', | ||
1492 | 'name', | ||
1493 | 'category', | ||
1494 | 'licence', | ||
1495 | 'language', | ||
1496 | 'privacy', | ||
1497 | 'nsfw', | ||
1498 | 'description', | ||
1499 | 'support', | ||
1500 | 'duration', | ||
1501 | 'views', | ||
1502 | 'likes', | ||
1503 | 'dislikes', | ||
1504 | 'remote', | ||
1505 | 'url', | ||
1506 | 'commentsEnabled', | ||
1507 | 'downloadEnabled', | ||
1508 | 'waitTranscoding', | ||
1509 | 'state', | ||
1510 | 'publishedAt', | ||
1511 | 'originallyPublishedAt', | ||
1512 | 'channelId', | ||
1513 | 'createdAt', | ||
1514 | 'updatedAt' | ||
1515 | ] | ||
1768 | 1516 | ||
1769 | const secondQuery: FindOptions = { | 1517 | function buildActor (rowActor: any) { |
1770 | offset: 0, | 1518 | const avatarModel = rowActor.Avatar.id !== null |
1771 | limit: query.limit, | 1519 | ? new AvatarModel(pick(rowActor.Avatar, avatarKeys)) |
1772 | attributes: query.attributes, | 1520 | : null |
1773 | order: [ // Keep original order | 1521 | |
1774 | Sequelize.literal( | 1522 | const serverModel = rowActor.Server.id !== null |
1775 | ids.map(id => `"VideoModel".id = ${id} DESC`).join(', ') | 1523 | ? new ServerModel(pick(rowActor.Server, serverKeys)) |
1776 | ) | 1524 | : null |
1777 | ] | ||
1778 | } | ||
1779 | 1525 | ||
1780 | const apiScope: (string | ScopeOptions)[] = [] | 1526 | const actorModel = new ActorModel(pick(rowActor, actorKeys)) |
1527 | actorModel.Avatar = avatarModel | ||
1528 | actorModel.Server = serverModel | ||
1781 | 1529 | ||
1782 | if (options.user) { | 1530 | return actorModel |
1783 | apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) | ||
1784 | } | 1531 | } |
1785 | 1532 | ||
1786 | apiScope.push({ | 1533 | for (const row of rows) { |
1787 | method: [ | 1534 | if (!memo[row.id]) { |
1788 | ScopeNames.FOR_API, { | 1535 | // Build Channel |
1789 | ids, | 1536 | const channel = row.VideoChannel |
1790 | withFiles: options.withFiles, | 1537 | const channelModel = new VideoChannelModel(pick(channel, [ 'id', 'name', 'description', 'actorId' ])) |
1791 | videoPlaylistId: options.videoPlaylistId | 1538 | channelModel.Actor = buildActor(channel.Actor) |
1792 | } as ForAPIOptions | 1539 | |
1793 | ] | 1540 | const account = row.VideoChannel.Account |
1794 | }) | 1541 | const accountModel = new AccountModel(pick(account, [ 'id', 'name' ])) |
1542 | accountModel.Actor = buildActor(account.Actor) | ||
1543 | |||
1544 | channelModel.Account = accountModel | ||
1545 | |||
1546 | const videoModel = new VideoModel(pick(row, videoKeys)) | ||
1547 | videoModel.VideoChannel = channelModel | ||
1795 | 1548 | ||
1796 | return VideoModel.scope(apiScope).findAll(secondQuery) | 1549 | videoModel.UserVideoHistories = [] |
1550 | videoModel.Thumbnails = [] | ||
1551 | videoModel.VideoFiles = [] | ||
1552 | |||
1553 | memo[row.id] = videoModel | ||
1554 | // Don't take object value to have a sorted array | ||
1555 | videos.push(videoModel) | ||
1556 | } | ||
1557 | |||
1558 | const videoModel = memo[row.id] | ||
1559 | |||
1560 | if (row.userVideoHistory?.id && !historyDone.has(row.userVideoHistory.id)) { | ||
1561 | const historyModel = new UserVideoHistoryModel(pick(row.userVideoHistory, [ 'id', 'currentTime' ])) | ||
1562 | videoModel.UserVideoHistories.push(historyModel) | ||
1563 | |||
1564 | historyDone.add(row.userVideoHistory.id) | ||
1565 | } | ||
1566 | |||
1567 | if (row.Thumbnails?.id && !thumbnailsDone.has(row.Thumbnails.id)) { | ||
1568 | const thumbnailModel = new ThumbnailModel(pick(row.Thumbnails, [ 'id', 'type', 'filename' ])) | ||
1569 | videoModel.Thumbnails.push(thumbnailModel) | ||
1570 | |||
1571 | thumbnailsDone.add(row.Thumbnails.id) | ||
1572 | } | ||
1573 | |||
1574 | if (row.VideoFiles?.id && !videoFilesDone.has(row.VideoFiles.id)) { | ||
1575 | const videoFileModel = new VideoFileModel(pick(row.VideoFiles, videoFileKeys)) | ||
1576 | videoModel.VideoFiles.push(videoFileModel) | ||
1577 | |||
1578 | videoFilesDone.add(row.VideoFiles.id) | ||
1579 | } | ||
1580 | } | ||
1581 | |||
1582 | return videos | ||
1797 | } | 1583 | } |
1798 | 1584 | ||
1799 | private static isPrivacyForFederation (privacy: VideoPrivacy) { | 1585 | private static isPrivacyForFederation (privacy: VideoPrivacy) { |
@@ -1803,23 +1589,23 @@ export class VideoModel extends Model<VideoModel> { | |||
1803 | } | 1589 | } |
1804 | 1590 | ||
1805 | static getCategoryLabel (id: number) { | 1591 | static getCategoryLabel (id: number) { |
1806 | return VIDEO_CATEGORIES[ id ] || 'Misc' | 1592 | return VIDEO_CATEGORIES[id] || 'Misc' |
1807 | } | 1593 | } |
1808 | 1594 | ||
1809 | static getLicenceLabel (id: number) { | 1595 | static getLicenceLabel (id: number) { |
1810 | return VIDEO_LICENCES[ id ] || 'Unknown' | 1596 | return VIDEO_LICENCES[id] || 'Unknown' |
1811 | } | 1597 | } |
1812 | 1598 | ||
1813 | static getLanguageLabel (id: string) { | 1599 | static getLanguageLabel (id: string) { |
1814 | return VIDEO_LANGUAGES[ id ] || 'Unknown' | 1600 | return VIDEO_LANGUAGES[id] || 'Unknown' |
1815 | } | 1601 | } |
1816 | 1602 | ||
1817 | static getPrivacyLabel (id: number) { | 1603 | static getPrivacyLabel (id: number) { |
1818 | return VIDEO_PRIVACIES[ id ] || 'Unknown' | 1604 | return VIDEO_PRIVACIES[id] || 'Unknown' |
1819 | } | 1605 | } |
1820 | 1606 | ||
1821 | static getStateLabel (id: number) { | 1607 | static getStateLabel (id: number) { |
1822 | return VIDEO_STATES[ id ] || 'Unknown' | 1608 | return VIDEO_STATES[id] || 'Unknown' |
1823 | } | 1609 | } |
1824 | 1610 | ||
1825 | isBlacklisted () { | 1611 | isBlacklisted () { |
@@ -1831,7 +1617,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1831 | this.VideoChannel.Account.isBlocked() | 1617 | this.VideoChannel.Account.isBlocked() |
1832 | } | 1618 | } |
1833 | 1619 | ||
1834 | getQualityFileBy <T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { | 1620 | getQualityFileBy<T extends MVideoWithFile> (this: T, fun: (files: MVideoFile[], it: (file: MVideoFile) => number) => MVideoFile) { |
1835 | if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) { | 1621 | if (Array.isArray(this.VideoFiles) && this.VideoFiles.length !== 0) { |
1836 | const file = fun(this.VideoFiles, file => file.resolution) | 1622 | const file = fun(this.VideoFiles, file => file.resolution) |
1837 | 1623 | ||
@@ -1849,15 +1635,15 @@ export class VideoModel extends Model<VideoModel> { | |||
1849 | return undefined | 1635 | return undefined |
1850 | } | 1636 | } |
1851 | 1637 | ||
1852 | getMaxQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { | 1638 | getMaxQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { |
1853 | return this.getQualityFileBy(maxBy) | 1639 | return this.getQualityFileBy(maxBy) |
1854 | } | 1640 | } |
1855 | 1641 | ||
1856 | getMinQualityFile <T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { | 1642 | getMinQualityFile<T extends MVideoWithFile> (this: T): MVideoFileVideo | MVideoFileStreamingPlaylistVideo { |
1857 | return this.getQualityFileBy(minBy) | 1643 | return this.getQualityFileBy(minBy) |
1858 | } | 1644 | } |
1859 | 1645 | ||
1860 | getWebTorrentFile <T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo { | 1646 | getWebTorrentFile<T extends MVideoWithFile> (this: T, resolution: number): MVideoFileVideo { |
1861 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1647 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1862 | 1648 | ||
1863 | const file = this.VideoFiles.find(f => f.resolution === resolution) | 1649 | const file = this.VideoFiles.find(f => f.resolution === resolution) |
@@ -1893,6 +1679,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1893 | return this.uuid + '.jpg' | 1679 | return this.uuid + '.jpg' |
1894 | } | 1680 | } |
1895 | 1681 | ||
1682 | hasPreview () { | ||
1683 | return !!this.getPreview() | ||
1684 | } | ||
1685 | |||
1896 | getPreview () { | 1686 | getPreview () { |
1897 | if (Array.isArray(this.Thumbnails) === false) return undefined | 1687 | if (Array.isArray(this.Thumbnails) === false) return undefined |
1898 | 1688 | ||
@@ -1980,8 +1770,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1980 | } | 1770 | } |
1981 | 1771 | ||
1982 | this.VideoStreamingPlaylists = this.VideoStreamingPlaylists | 1772 | this.VideoStreamingPlaylists = this.VideoStreamingPlaylists |
1983 | .filter(s => s.type !== VideoStreamingPlaylistType.HLS) | 1773 | .filter(s => s.type !== VideoStreamingPlaylistType.HLS) |
1984 | .concat(toAdd) | 1774 | .concat(toAdd) |
1985 | } | 1775 | } |
1986 | 1776 | ||
1987 | removeFile (videoFile: MVideoFile, isRedundancy = false) { | 1777 | removeFile (videoFile: MVideoFile, isRedundancy = false) { |
@@ -2002,7 +1792,7 @@ export class VideoModel extends Model<VideoModel> { | |||
2002 | await remove(directoryPath) | 1792 | await remove(directoryPath) |
2003 | 1793 | ||
2004 | if (isRedundancy !== true) { | 1794 | if (isRedundancy !== true) { |
2005 | let streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo | 1795 | const streamingPlaylistWithFiles = streamingPlaylist as MStreamingPlaylistFilesVideo |
2006 | streamingPlaylistWithFiles.Video = this | 1796 | streamingPlaylistWithFiles.Video = this |
2007 | 1797 | ||
2008 | if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) { | 1798 | if (!Array.isArray(streamingPlaylistWithFiles.VideoFiles)) { |
@@ -2096,6 +1886,14 @@ export class VideoModel extends Model<VideoModel> { | |||
2096 | return baseUrlHttp + STATIC_PATHS.WEBSEED + getVideoFilename(this, videoFile) | 1886 | return baseUrlHttp + STATIC_PATHS.WEBSEED + getVideoFilename(this, videoFile) |
2097 | } | 1887 | } |
2098 | 1888 | ||
1889 | getVideoFileMetadataUrl (videoFile: MVideoFile, baseUrlHttp: string) { | ||
1890 | const path = '/api/v1/videos/' | ||
1891 | |||
1892 | return this.isOwned() | ||
1893 | ? baseUrlHttp + path + this.uuid + '/metadata/' + videoFile.id | ||
1894 | : videoFile.metadataUrl | ||
1895 | } | ||
1896 | |||
2099 | getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) { | 1897 | getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
2100 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + getVideoFilename(this, videoFile) | 1898 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + getVideoFilename(this, videoFile) |
2101 | } | 1899 | } |
diff --git a/server/tests/api/activitypub/client.ts b/server/tests/api/activitypub/client.ts index 34c6be49b..d16f05108 100644 --- a/server/tests/api/activitypub/client.ts +++ b/server/tests/api/activitypub/client.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -6,8 +6,6 @@ import { | |||
6 | cleanupTests, | 6 | cleanupTests, |
7 | doubleFollow, | 7 | doubleFollow, |
8 | flushAndRunMultipleServers, | 8 | flushAndRunMultipleServers, |
9 | flushTests, | ||
10 | killallServers, | ||
11 | makeActivityPubGetRequest, | 9 | makeActivityPubGetRequest, |
12 | ServerInfo, | 10 | ServerInfo, |
13 | setAccessTokensToServers, | 11 | setAccessTokensToServers, |
diff --git a/server/tests/api/activitypub/fetch.ts b/server/tests/api/activitypub/fetch.ts index 3d54c2042..35fd94eed 100644 --- a/server/tests/api/activitypub/fetch.ts +++ b/server/tests/api/activitypub/fetch.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
@@ -8,9 +8,7 @@ import { | |||
8 | createUser, | 8 | createUser, |
9 | doubleFollow, | 9 | doubleFollow, |
10 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
11 | flushTests, | ||
12 | getVideosListSort, | 11 | getVideosListSort, |
13 | killallServers, | ||
14 | ServerInfo, | 12 | ServerInfo, |
15 | setAccessTokensToServers, | 13 | setAccessTokensToServers, |
16 | setActorField, | 14 | setActorField, |
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts index 8c00ba3d6..60d95b823 100644 --- a/server/tests/api/activitypub/helpers.ts +++ b/server/tests/api/activitypub/helpers.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { expect } from 'chai' | 4 | import { expect } from 'chai' |
diff --git a/server/tests/api/activitypub/refresher.ts b/server/tests/api/activitypub/refresher.ts index aa4bc6c0f..232c5d823 100644 --- a/server/tests/api/activitypub/refresher.ts +++ b/server/tests/api/activitypub/refresher.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { | 4 | import { |
@@ -43,32 +43,32 @@ describe('Test AP refresher', function () { | |||
43 | await setDefaultVideoChannel(servers) | 43 | await setDefaultVideoChannel(servers) |
44 | 44 | ||
45 | { | 45 | { |
46 | videoUUID1 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video1' })).uuid | 46 | videoUUID1 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video1' })).uuid |
47 | videoUUID2 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video2' })).uuid | 47 | videoUUID2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video2' })).uuid |
48 | videoUUID3 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video3' })).uuid | 48 | videoUUID3 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video3' })).uuid |
49 | } | 49 | } |
50 | 50 | ||
51 | { | 51 | { |
52 | const a1 = await generateUserAccessToken(servers[ 1 ], 'user1') | 52 | const a1 = await generateUserAccessToken(servers[1], 'user1') |
53 | await uploadVideo(servers[ 1 ].url, a1, { name: 'video4' }) | 53 | await uploadVideo(servers[1].url, a1, { name: 'video4' }) |
54 | 54 | ||
55 | const a2 = await generateUserAccessToken(servers[ 1 ], 'user2') | 55 | const a2 = await generateUserAccessToken(servers[1], 'user2') |
56 | await uploadVideo(servers[ 1 ].url, a2, { name: 'video5' }) | 56 | await uploadVideo(servers[1].url, a2, { name: 'video5' }) |
57 | } | 57 | } |
58 | 58 | ||
59 | { | 59 | { |
60 | const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[ 1 ].videoChannel.id } | 60 | const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id } |
61 | const res = await createVideoPlaylist({ url: servers[ 1 ].url, token: servers[ 1 ].accessToken, playlistAttrs }) | 61 | const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs }) |
62 | playlistUUID1 = res.body.videoPlaylist.uuid | 62 | playlistUUID1 = res.body.videoPlaylist.uuid |
63 | } | 63 | } |
64 | 64 | ||
65 | { | 65 | { |
66 | const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[ 1 ].videoChannel.id } | 66 | const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id } |
67 | const res = await createVideoPlaylist({ url: servers[ 1 ].url, token: servers[ 1 ].accessToken, playlistAttrs }) | 67 | const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs }) |
68 | playlistUUID2 = res.body.videoPlaylist.uuid | 68 | playlistUUID2 = res.body.videoPlaylist.uuid |
69 | } | 69 | } |
70 | 70 | ||
71 | await doubleFollow(servers[ 0 ], servers[ 1 ]) | 71 | await doubleFollow(servers[0], servers[1]) |
72 | }) | 72 | }) |
73 | 73 | ||
74 | describe('Videos refresher', function () { | 74 | describe('Videos refresher', function () { |
@@ -79,34 +79,34 @@ describe('Test AP refresher', function () { | |||
79 | await wait(10000) | 79 | await wait(10000) |
80 | 80 | ||
81 | // Change UUID so the remote server returns a 404 | 81 | // Change UUID so the remote server returns a 404 |
82 | await setVideoField(servers[ 1 ].internalServerNumber, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f') | 82 | await setVideoField(servers[1].internalServerNumber, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f') |
83 | 83 | ||
84 | await getVideo(servers[ 0 ].url, videoUUID1) | 84 | await getVideo(servers[0].url, videoUUID1) |
85 | await getVideo(servers[ 0 ].url, videoUUID2) | 85 | await getVideo(servers[0].url, videoUUID2) |
86 | 86 | ||
87 | await waitJobs(servers) | 87 | await waitJobs(servers) |
88 | 88 | ||
89 | await getVideo(servers[ 0 ].url, videoUUID1, 404) | 89 | await getVideo(servers[0].url, videoUUID1, 404) |
90 | await getVideo(servers[ 0 ].url, videoUUID2, 200) | 90 | await getVideo(servers[0].url, videoUUID2, 200) |
91 | }) | 91 | }) |
92 | 92 | ||
93 | it('Should not update a remote video if the remote instance is down', async function () { | 93 | it('Should not update a remote video if the remote instance is down', async function () { |
94 | this.timeout(70000) | 94 | this.timeout(70000) |
95 | 95 | ||
96 | killallServers([ servers[ 1 ] ]) | 96 | killallServers([ servers[1] ]) |
97 | 97 | ||
98 | await setVideoField(servers[ 1 ].internalServerNumber, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e') | 98 | await setVideoField(servers[1].internalServerNumber, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e') |
99 | 99 | ||
100 | // Video will need a refresh | 100 | // Video will need a refresh |
101 | await wait(10000) | 101 | await wait(10000) |
102 | 102 | ||
103 | await getVideo(servers[ 0 ].url, videoUUID3) | 103 | await getVideo(servers[0].url, videoUUID3) |
104 | // The refresh should fail | 104 | // The refresh should fail |
105 | await waitJobs([ servers[ 0 ] ]) | 105 | await waitJobs([ servers[0] ]) |
106 | 106 | ||
107 | await reRunServer(servers[ 1 ]) | 107 | await reRunServer(servers[1]) |
108 | 108 | ||
109 | await getVideo(servers[ 0 ].url, videoUUID3, 200) | 109 | await getVideo(servers[0].url, videoUUID3, 200) |
110 | }) | 110 | }) |
111 | }) | 111 | }) |
112 | 112 | ||
@@ -118,16 +118,16 @@ describe('Test AP refresher', function () { | |||
118 | await wait(10000) | 118 | await wait(10000) |
119 | 119 | ||
120 | // Change actor name so the remote server returns a 404 | 120 | // Change actor name so the remote server returns a 404 |
121 | const to = 'http://localhost:' + servers[ 1 ].port + '/accounts/user2' | 121 | const to = 'http://localhost:' + servers[1].port + '/accounts/user2' |
122 | await setActorField(servers[ 1 ].internalServerNumber, to, 'preferredUsername', 'toto') | 122 | await setActorField(servers[1].internalServerNumber, to, 'preferredUsername', 'toto') |
123 | 123 | ||
124 | await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port) | 124 | await getAccount(servers[0].url, 'user1@localhost:' + servers[1].port) |
125 | await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port) | 125 | await getAccount(servers[0].url, 'user2@localhost:' + servers[1].port) |
126 | 126 | ||
127 | await waitJobs(servers) | 127 | await waitJobs(servers) |
128 | 128 | ||
129 | await getAccount(servers[ 0 ].url, 'user1@localhost:' + servers[ 1 ].port, 200) | 129 | await getAccount(servers[0].url, 'user1@localhost:' + servers[1].port, 200) |
130 | await getAccount(servers[ 0 ].url, 'user2@localhost:' + servers[ 1 ].port, 404) | 130 | await getAccount(servers[0].url, 'user2@localhost:' + servers[1].port, 404) |
131 | }) | 131 | }) |
132 | }) | 132 | }) |
133 | 133 | ||
@@ -139,15 +139,15 @@ describe('Test AP refresher', function () { | |||
139 | await wait(10000) | 139 | await wait(10000) |
140 | 140 | ||
141 | // Change UUID so the remote server returns a 404 | 141 | // Change UUID so the remote server returns a 404 |
142 | await setPlaylistField(servers[ 1 ].internalServerNumber, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e') | 142 | await setPlaylistField(servers[1].internalServerNumber, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e') |
143 | 143 | ||
144 | await getVideoPlaylist(servers[ 0 ].url, playlistUUID1) | 144 | await getVideoPlaylist(servers[0].url, playlistUUID1) |
145 | await getVideoPlaylist(servers[ 0 ].url, playlistUUID2) | 145 | await getVideoPlaylist(servers[0].url, playlistUUID2) |
146 | 146 | ||
147 | await waitJobs(servers) | 147 | await waitJobs(servers) |
148 | 148 | ||
149 | await getVideoPlaylist(servers[ 0 ].url, playlistUUID1, 200) | 149 | await getVideoPlaylist(servers[0].url, playlistUUID1, 200) |
150 | await getVideoPlaylist(servers[ 0 ].url, playlistUUID2, 404) | 150 | await getVideoPlaylist(servers[0].url, playlistUUID2, 404) |
151 | }) | 151 | }) |
152 | }) | 152 | }) |
153 | 153 | ||
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts index dc960c5c3..ac4bc7c6a 100644 --- a/server/tests/api/activitypub/security.ts +++ b/server/tests/api/activitypub/security.ts | |||
@@ -1,20 +1,14 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { cleanupTests, closeAllSequelize, flushAndRunMultipleServers, ServerInfo, setActorField } from '../../../../shared/extra-utils' |
6 | cleanupTests, | ||
7 | closeAllSequelize, | ||
8 | flushAndRunMultipleServers, | ||
9 | killallServers, | ||
10 | ServerInfo, | ||
11 | setActorField | ||
12 | } from '../../../../shared/extra-utils' | ||
13 | import { HTTP_SIGNATURE } from '../../../initializers/constants' | 6 | import { HTTP_SIGNATURE } from '../../../initializers/constants' |
14 | import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils' | 7 | import { buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils' |
15 | import * as chai from 'chai' | 8 | import * as chai from 'chai' |
16 | import { activityPubContextify, buildSignedActivity } from '../../../helpers/activitypub' | 9 | import { activityPubContextify, buildSignedActivity } from '../../../helpers/activitypub' |
17 | import { makeFollowRequest, makePOSTAPRequest } from '../../../../shared/extra-utils/requests/activitypub' | 10 | import { makeFollowRequest, makePOSTAPRequest } from '../../../../shared/extra-utils/requests/activitypub' |
11 | import { buildDigest } from '@server/helpers/peertube-crypto' | ||
18 | 12 | ||
19 | const expect = chai.expect | 13 | const expect = chai.expect |
20 | 14 | ||
@@ -33,7 +27,7 @@ function getAnnounceWithoutContext (server2: ServerInfo) { | |||
33 | if (Array.isArray(json[key])) { | 27 | if (Array.isArray(json[key])) { |
34 | result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`)) | 28 | result[key] = json[key].map(v => v.replace(':9002', `:${server2.port}`)) |
35 | } else { | 29 | } else { |
36 | result[ key ] = json[ key ].replace(':9002', `:${server2.port}`) | 30 | result[key] = json[key].replace(':9002', `:${server2.port}`) |
37 | } | 31 | } |
38 | } | 32 | } |
39 | 33 | ||
diff --git a/server/tests/api/check-params/accounts.ts b/server/tests/api/check-params/accounts.ts index 4f79685bd..c29af7cd7 100644 --- a/server/tests/api/check-params/accounts.ts +++ b/server/tests/api/check-params/accounts.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts index 0661676ce..1219ec9bd 100644 --- a/server/tests/api/check-params/blocklist.ts +++ b/server/tests/api/check-params/blocklist.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
@@ -175,13 +175,13 @@ describe('Test blocklist API validators', function () { | |||
175 | }) | 175 | }) |
176 | }) | 176 | }) |
177 | 177 | ||
178 | it('Should fail with an unknown server', async function () { | 178 | it('Should succeed with an unknown server', async function () { |
179 | await makePostBodyRequest({ | 179 | await makePostBodyRequest({ |
180 | url: server.url, | 180 | url: server.url, |
181 | token: server.accessToken, | 181 | token: server.accessToken, |
182 | path, | 182 | path, |
183 | fields: { host: 'localhost:9003' }, | 183 | fields: { host: 'localhost:9003' }, |
184 | statusCodeExpected: 404 | 184 | statusCodeExpected: 204 |
185 | }) | 185 | }) |
186 | }) | 186 | }) |
187 | 187 | ||
@@ -218,7 +218,7 @@ describe('Test blocklist API validators', function () { | |||
218 | it('Should fail with an unknown server block', async function () { | 218 | it('Should fail with an unknown server block', async function () { |
219 | await makeDeleteRequest({ | 219 | await makeDeleteRequest({ |
220 | url: server.url, | 220 | url: server.url, |
221 | path: path + '/localhost:9003', | 221 | path: path + '/localhost:9004', |
222 | token: server.accessToken, | 222 | token: server.accessToken, |
223 | statusCodeExpected: 404 | 223 | statusCodeExpected: 404 |
224 | }) | 224 | }) |
@@ -415,13 +415,13 @@ describe('Test blocklist API validators', function () { | |||
415 | }) | 415 | }) |
416 | }) | 416 | }) |
417 | 417 | ||
418 | it('Should fail with an unknown server', async function () { | 418 | it('Should succeed with an unknown server', async function () { |
419 | await makePostBodyRequest({ | 419 | await makePostBodyRequest({ |
420 | url: server.url, | 420 | url: server.url, |
421 | token: server.accessToken, | 421 | token: server.accessToken, |
422 | path, | 422 | path, |
423 | fields: { host: 'localhost:9003' }, | 423 | fields: { host: 'localhost:9003' }, |
424 | statusCodeExpected: 404 | 424 | statusCodeExpected: 204 |
425 | }) | 425 | }) |
426 | }) | 426 | }) |
427 | 427 | ||
@@ -467,7 +467,7 @@ describe('Test blocklist API validators', function () { | |||
467 | it('Should fail with an unknown server block', async function () { | 467 | it('Should fail with an unknown server block', async function () { |
468 | await makeDeleteRequest({ | 468 | await makeDeleteRequest({ |
469 | url: server.url, | 469 | url: server.url, |
470 | path: path + '/localhost:9003', | 470 | path: path + '/localhost:9004', |
471 | token: server.accessToken, | 471 | token: server.accessToken, |
472 | statusCodeExpected: 404 | 472 | statusCodeExpected: 404 |
473 | }) | 473 | }) |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index 443fbcb60..f1a79806b 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import { omit } from 'lodash' | 3 | import { omit } from 'lodash' |
4 | import 'mocha' | 4 | import 'mocha' |
diff --git a/server/tests/api/check-params/contact-form.ts b/server/tests/api/check-params/contact-form.ts index b3051945e..b2126b9b0 100644 --- a/server/tests/api/check-params/contact-form.ts +++ b/server/tests/api/check-params/contact-form.ts | |||
@@ -1,22 +1,8 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { cleanupTests, flushAndRunServer, immutableAssign, killallServers, reRunServer, ServerInfo } from '../../../../shared/extra-utils' |
6 | flushTests, | ||
7 | immutableAssign, | ||
8 | killallServers, | ||
9 | reRunServer, | ||
10 | flushAndRunServer, | ||
11 | ServerInfo, | ||
12 | setAccessTokensToServers, cleanupTests | ||
13 | } from '../../../../shared/extra-utils' | ||
14 | import { | ||
15 | checkBadCountPagination, | ||
16 | checkBadSortPagination, | ||
17 | checkBadStartPagination | ||
18 | } from '../../../../shared/extra-utils/requests/check-api-params' | ||
19 | import { getAccount } from '../../../../shared/extra-utils/users/accounts' | ||
20 | import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' | 6 | import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' |
21 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' | 7 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' |
22 | 8 | ||
diff --git a/server/tests/api/check-params/debug.ts b/server/tests/api/check-params/debug.ts index 8dad26723..5fac73485 100644 --- a/server/tests/api/check-params/debug.ts +++ b/server/tests/api/check-params/debug.ts | |||
@@ -1,15 +1,14 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | flushTests, | ||
8 | killallServers, | ||
9 | flushAndRunServer, | 8 | flushAndRunServer, |
10 | ServerInfo, | 9 | ServerInfo, |
11 | setAccessTokensToServers, | 10 | setAccessTokensToServers, |
12 | userLogin, cleanupTests | 11 | userLogin |
13 | } from '../../../../shared/extra-utils' | 12 | } from '../../../../shared/extra-utils' |
14 | import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' | 13 | import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' |
15 | 14 | ||
diff --git a/server/tests/api/check-params/follows.ts b/server/tests/api/check-params/follows.ts index be2a603a3..2c2224a45 100644 --- a/server/tests/api/check-params/follows.ts +++ b/server/tests/api/check-params/follows.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 924c0df76..ef152f55c 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts | |||
@@ -23,3 +23,4 @@ import './video-playlists' | |||
23 | import './videos' | 23 | import './videos' |
24 | import './videos-filter' | 24 | import './videos-filter' |
25 | import './videos-history' | 25 | import './videos-history' |
26 | import './videos-overviews' | ||
diff --git a/server/tests/api/check-params/jobs.ts b/server/tests/api/check-params/jobs.ts index 22e237964..8f4af8d16 100644 --- a/server/tests/api/check-params/jobs.ts +++ b/server/tests/api/check-params/jobs.ts | |||
@@ -1,16 +1,14 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | flushTests, | ||
8 | killallServers, | ||
9 | flushAndRunServer, | 8 | flushAndRunServer, |
10 | ServerInfo, | 9 | ServerInfo, |
11 | setAccessTokensToServers, | 10 | setAccessTokensToServers, |
12 | userLogin, | 11 | userLogin |
13 | cleanupTests | ||
14 | } from '../../../../shared/extra-utils' | 12 | } from '../../../../shared/extra-utils' |
15 | import { | 13 | import { |
16 | checkBadCountPagination, | 14 | checkBadCountPagination, |
diff --git a/server/tests/api/check-params/logs.ts b/server/tests/api/check-params/logs.ts index f9d96bcc0..719da54e6 100644 --- a/server/tests/api/check-params/logs.ts +++ b/server/tests/api/check-params/logs.ts | |||
@@ -1,16 +1,14 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | flushTests, | ||
8 | killallServers, | ||
9 | flushAndRunServer, | 8 | flushAndRunServer, |
10 | ServerInfo, | 9 | ServerInfo, |
11 | setAccessTokensToServers, | 10 | setAccessTokensToServers, |
12 | userLogin, | 11 | userLogin |
13 | cleanupTests | ||
14 | } from '../../../../shared/extra-utils' | 12 | } from '../../../../shared/extra-utils' |
15 | import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' | 13 | import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' |
16 | 14 | ||
diff --git a/server/tests/api/check-params/plugins.ts b/server/tests/api/check-params/plugins.ts index 9553bce17..07ded26ee 100644 --- a/server/tests/api/check-params/plugins.ts +++ b/server/tests/api/check-params/plugins.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
@@ -64,6 +64,7 @@ describe('Test server plugins API validators', function () { | |||
64 | describe('With static plugin routes', function () { | 64 | describe('With static plugin routes', function () { |
65 | it('Should fail with an unknown plugin name/plugin version', async function () { | 65 | it('Should fail with an unknown plugin name/plugin version', async function () { |
66 | const paths = [ | 66 | const paths = [ |
67 | '/plugins/' + pluginName + '/0.0.1/auth/fake-auth', | ||
67 | '/plugins/' + pluginName + '/0.0.1/static/images/chocobo.png', | 68 | '/plugins/' + pluginName + '/0.0.1/static/images/chocobo.png', |
68 | '/plugins/' + pluginName + '/0.0.1/client-scripts/client/common-client-plugin.js', | 69 | '/plugins/' + pluginName + '/0.0.1/client-scripts/client/common-client-plugin.js', |
69 | '/themes/' + themeName + '/0.0.1/static/images/chocobo.png', | 70 | '/themes/' + themeName + '/0.0.1/static/images/chocobo.png', |
@@ -86,6 +87,7 @@ describe('Test server plugins API validators', function () { | |||
86 | 87 | ||
87 | it('Should fail with invalid versions', async function () { | 88 | it('Should fail with invalid versions', async function () { |
88 | const paths = [ | 89 | const paths = [ |
90 | '/plugins/' + pluginName + '/0.0.1.1/auth/fake-auth', | ||
89 | '/plugins/' + pluginName + '/0.0.1.1/static/images/chocobo.png', | 91 | '/plugins/' + pluginName + '/0.0.1.1/static/images/chocobo.png', |
90 | '/plugins/' + pluginName + '/0.1/client-scripts/client/common-client-plugin.js', | 92 | '/plugins/' + pluginName + '/0.1/client-scripts/client/common-client-plugin.js', |
91 | '/themes/' + themeName + '/1/static/images/chocobo.png', | 93 | '/themes/' + themeName + '/1/static/images/chocobo.png', |
@@ -112,6 +114,12 @@ describe('Test server plugins API validators', function () { | |||
112 | } | 114 | } |
113 | }) | 115 | }) |
114 | 116 | ||
117 | it('Should fail with an unknown auth name', async function () { | ||
118 | const path = '/plugins/' + pluginName + '/' + npmVersion + '/auth/bad-auth' | ||
119 | |||
120 | await makeGetRequest({ url: server.url, path, statusCodeExpected: 404 }) | ||
121 | }) | ||
122 | |||
115 | it('Should fail with an unknown static file', async function () { | 123 | it('Should fail with an unknown static file', async function () { |
116 | const paths = [ | 124 | const paths = [ |
117 | '/plugins/' + pluginName + '/' + npmVersion + '/static/fake/chocobo.png', | 125 | '/plugins/' + pluginName + '/' + npmVersion + '/static/fake/chocobo.png', |
@@ -145,6 +153,9 @@ describe('Test server plugins API validators', function () { | |||
145 | for (const p of paths) { | 153 | for (const p of paths) { |
146 | await makeGetRequest({ url: server.url, path: p, statusCodeExpected: 200 }) | 154 | await makeGetRequest({ url: server.url, path: p, statusCodeExpected: 200 }) |
147 | } | 155 | } |
156 | |||
157 | const authPath = '/plugins/' + pluginName + '/' + npmVersion + '/auth/fake-auth' | ||
158 | await makeGetRequest({ url: server.url, path: authPath, statusCodeExpected: 302 }) | ||
148 | }) | 159 | }) |
149 | }) | 160 | }) |
150 | 161 | ||
@@ -462,6 +473,8 @@ describe('Test server plugins API validators', function () { | |||
462 | }) | 473 | }) |
463 | 474 | ||
464 | it('Should succeed with the correct parameters', async function () { | 475 | it('Should succeed with the correct parameters', async function () { |
476 | this.timeout(10000) | ||
477 | |||
465 | const it = [ | 478 | const it = [ |
466 | { suffix: 'install', status: 200 }, | 479 | { suffix: 'install', status: 200 }, |
467 | { suffix: 'update', status: 200 }, | 480 | { suffix: 'update', status: 200 }, |
diff --git a/server/tests/api/check-params/redundancy.ts b/server/tests/api/check-params/redundancy.ts index 6471da840..b2370a094 100644 --- a/server/tests/api/check-params/redundancy.ts +++ b/server/tests/api/check-params/redundancy.ts | |||
@@ -1,23 +1,27 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | checkBadCountPagination, | ||
7 | checkBadSortPagination, | ||
8 | checkBadStartPagination, | ||
6 | cleanupTests, | 9 | cleanupTests, |
7 | createUser, | 10 | createUser, |
8 | doubleFollow, | 11 | doubleFollow, |
9 | flushAndRunMultipleServers, | 12 | flushAndRunMultipleServers, makeDeleteRequest, |
10 | flushTests, | 13 | makeGetRequest, makePostBodyRequest, |
11 | killallServers, | ||
12 | makePutBodyRequest, | 14 | makePutBodyRequest, |
13 | ServerInfo, | 15 | ServerInfo, |
14 | setAccessTokensToServers, | 16 | setAccessTokensToServers, uploadVideoAndGetId, |
15 | userLogin | 17 | userLogin, waitJobs |
16 | } from '../../../../shared/extra-utils' | 18 | } from '../../../../shared/extra-utils' |
17 | 19 | ||
18 | describe('Test server redundancy API validators', function () { | 20 | describe('Test server redundancy API validators', function () { |
19 | let servers: ServerInfo[] | 21 | let servers: ServerInfo[] |
20 | let userAccessToken = null | 22 | let userAccessToken = null |
23 | let videoIdLocal: number | ||
24 | let videoIdRemote: number | ||
21 | 25 | ||
22 | // --------------------------------------------------------------- | 26 | // --------------------------------------------------------------- |
23 | 27 | ||
@@ -34,11 +38,136 @@ describe('Test server redundancy API validators', function () { | |||
34 | password: 'password' | 38 | password: 'password' |
35 | } | 39 | } |
36 | 40 | ||
37 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) | 41 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) |
38 | userAccessToken = await userLogin(servers[0], user) | 42 | userAccessToken = await userLogin(servers[0], user) |
43 | |||
44 | videoIdLocal = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video' })).id | ||
45 | videoIdRemote = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video' })).id | ||
46 | |||
47 | await waitJobs(servers) | ||
48 | }) | ||
49 | |||
50 | describe('When listing redundancies', function () { | ||
51 | const path = '/api/v1/server/redundancy/videos' | ||
52 | |||
53 | let url: string | ||
54 | let token: string | ||
55 | |||
56 | before(function () { | ||
57 | url = servers[0].url | ||
58 | token = servers[0].accessToken | ||
59 | }) | ||
60 | |||
61 | it('Should fail with an invalid token', async function () { | ||
62 | await makeGetRequest({ url, path, token: 'fake_token', statusCodeExpected: 401 }) | ||
63 | }) | ||
64 | |||
65 | it('Should fail if the user is not an administrator', async function () { | ||
66 | await makeGetRequest({ url, path, token: userAccessToken, statusCodeExpected: 403 }) | ||
67 | }) | ||
68 | |||
69 | it('Should fail with a bad start pagination', async function () { | ||
70 | await checkBadStartPagination(url, path, servers[0].accessToken) | ||
71 | }) | ||
72 | |||
73 | it('Should fail with a bad count pagination', async function () { | ||
74 | await checkBadCountPagination(url, path, servers[0].accessToken) | ||
75 | }) | ||
76 | |||
77 | it('Should fail with an incorrect sort', async function () { | ||
78 | await checkBadSortPagination(url, path, servers[0].accessToken) | ||
79 | }) | ||
80 | |||
81 | it('Should fail with a bad target', async function () { | ||
82 | await makeGetRequest({ url, path, token, query: { target: 'bad target' } }) | ||
83 | }) | ||
84 | |||
85 | it('Should fail without target', async function () { | ||
86 | await makeGetRequest({ url, path, token }) | ||
87 | }) | ||
88 | |||
89 | it('Should succeed with the correct params', async function () { | ||
90 | await makeGetRequest({ url, path, token, query: { target: 'my-videos' }, statusCodeExpected: 200 }) | ||
91 | }) | ||
92 | }) | ||
93 | |||
94 | describe('When manually adding a redundancy', function () { | ||
95 | const path = '/api/v1/server/redundancy/videos' | ||
96 | |||
97 | let url: string | ||
98 | let token: string | ||
99 | |||
100 | before(function () { | ||
101 | url = servers[0].url | ||
102 | token = servers[0].accessToken | ||
103 | }) | ||
104 | |||
105 | it('Should fail with an invalid token', async function () { | ||
106 | await makePostBodyRequest({ url, path, token: 'fake_token', statusCodeExpected: 401 }) | ||
107 | }) | ||
108 | |||
109 | it('Should fail if the user is not an administrator', async function () { | ||
110 | await makePostBodyRequest({ url, path, token: userAccessToken, statusCodeExpected: 403 }) | ||
111 | }) | ||
112 | |||
113 | it('Should fail without a video id', async function () { | ||
114 | await makePostBodyRequest({ url, path, token }) | ||
115 | }) | ||
116 | |||
117 | it('Should fail with an incorrect video id', async function () { | ||
118 | await makePostBodyRequest({ url, path, token, fields: { videoId: 'peertube' } }) | ||
119 | }) | ||
120 | |||
121 | it('Should fail with a not found video id', async function () { | ||
122 | await makePostBodyRequest({ url, path, token, fields: { videoId: 6565 }, statusCodeExpected: 404 }) | ||
123 | }) | ||
124 | |||
125 | it('Should fail with a local a video id', async function () { | ||
126 | await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdLocal } }) | ||
127 | }) | ||
128 | |||
129 | it('Should succeed with the correct params', async function () { | ||
130 | await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: 204 }) | ||
131 | }) | ||
132 | |||
133 | it('Should fail if the video is already duplicated', async function () { | ||
134 | this.timeout(30000) | ||
135 | |||
136 | await waitJobs(servers) | ||
137 | |||
138 | await makePostBodyRequest({ url, path, token, fields: { videoId: videoIdRemote }, statusCodeExpected: 409 }) | ||
139 | }) | ||
140 | }) | ||
141 | |||
142 | describe('When manually removing a redundancy', function () { | ||
143 | const path = '/api/v1/server/redundancy/videos/' | ||
144 | |||
145 | let url: string | ||
146 | let token: string | ||
147 | |||
148 | before(function () { | ||
149 | url = servers[0].url | ||
150 | token = servers[0].accessToken | ||
151 | }) | ||
152 | |||
153 | it('Should fail with an invalid token', async function () { | ||
154 | await makeDeleteRequest({ url, path: path + '1', token: 'fake_token', statusCodeExpected: 401 }) | ||
155 | }) | ||
156 | |||
157 | it('Should fail if the user is not an administrator', async function () { | ||
158 | await makeDeleteRequest({ url, path: path + '1', token: userAccessToken, statusCodeExpected: 403 }) | ||
159 | }) | ||
160 | |||
161 | it('Should fail with an incorrect video id', async function () { | ||
162 | await makeDeleteRequest({ url, path: path + 'toto', token }) | ||
163 | }) | ||
164 | |||
165 | it('Should fail with a not found video redundancy', async function () { | ||
166 | await makeDeleteRequest({ url, path: path + '454545', token, statusCodeExpected: 404 }) | ||
167 | }) | ||
39 | }) | 168 | }) |
40 | 169 | ||
41 | describe('When updating redundancy', function () { | 170 | describe('When updating server redundancy', function () { |
42 | const path = '/api/v1/server/redundancy' | 171 | const path = '/api/v1/server/redundancy' |
43 | 172 | ||
44 | it('Should fail with an invalid token', async function () { | 173 | it('Should fail with an invalid token', async function () { |
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts index 8ad9d98bf..f8d0cd4ec 100644 --- a/server/tests/api/check-params/search.ts +++ b/server/tests/api/check-params/search.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
diff --git a/server/tests/api/check-params/services.ts b/server/tests/api/check-params/services.ts index d15753aed..457adfaab 100644 --- a/server/tests/api/check-params/services.ts +++ b/server/tests/api/check-params/services.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts index 3b06be7ef..2048fa667 100644 --- a/server/tests/api/check-params/user-notifications.ts +++ b/server/tests/api/check-params/user-notifications.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as io from 'socket.io-client' | 4 | import * as io from 'socket.io-client' |
diff --git a/server/tests/api/check-params/user-subscriptions.ts b/server/tests/api/check-params/user-subscriptions.ts index fa36c4078..1edba4d64 100644 --- a/server/tests/api/check-params/user-subscriptions.ts +++ b/server/tests/api/check-params/user-subscriptions.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 5d5af284c..4d597f0a3 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import { omit } from 'lodash' | 3 | import { omit } from 'lodash' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -16,12 +16,14 @@ import { | |||
16 | getMyUserVideoRating, | 16 | getMyUserVideoRating, |
17 | getUsersList, | 17 | getUsersList, |
18 | immutableAssign, | 18 | immutableAssign, |
19 | killallServers, | ||
19 | makeGetRequest, | 20 | makeGetRequest, |
20 | makePostBodyRequest, | 21 | makePostBodyRequest, |
21 | makePutBodyRequest, | 22 | makePutBodyRequest, |
22 | makeUploadRequest, | 23 | makeUploadRequest, |
23 | registerUser, | 24 | registerUser, |
24 | removeUser, | 25 | removeUser, |
26 | reRunServer, | ||
25 | ServerInfo, | 27 | ServerInfo, |
26 | setAccessTokensToServers, | 28 | setAccessTokensToServers, |
27 | unblockUser, | 29 | unblockUser, |
@@ -39,6 +41,7 @@ import { VideoPrivacy } from '../../../../shared/models/videos' | |||
39 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 41 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
40 | import { expect } from 'chai' | 42 | import { expect } from 'chai' |
41 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | 43 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' |
44 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' | ||
42 | 45 | ||
43 | describe('Test users API validators', function () { | 46 | describe('Test users API validators', function () { |
44 | const path = '/api/v1/users/' | 47 | const path = '/api/v1/users/' |
@@ -50,6 +53,9 @@ describe('Test users API validators', function () { | |||
50 | let serverWithRegistrationDisabled: ServerInfo | 53 | let serverWithRegistrationDisabled: ServerInfo |
51 | let userAccessToken = '' | 54 | let userAccessToken = '' |
52 | let moderatorAccessToken = '' | 55 | let moderatorAccessToken = '' |
56 | let emailPort: number | ||
57 | let overrideConfig: Object | ||
58 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
53 | let channelId: number | 59 | let channelId: number |
54 | 60 | ||
55 | // --------------------------------------------------------------- | 61 | // --------------------------------------------------------------- |
@@ -57,9 +63,14 @@ describe('Test users API validators', function () { | |||
57 | before(async function () { | 63 | before(async function () { |
58 | this.timeout(30000) | 64 | this.timeout(30000) |
59 | 65 | ||
66 | const emails: object[] = [] | ||
67 | emailPort = await MockSmtpServer.Instance.collectEmails(emails) | ||
68 | |||
69 | overrideConfig = { signup: { limit: 8 } } | ||
70 | |||
60 | { | 71 | { |
61 | const res = await Promise.all([ | 72 | const res = await Promise.all([ |
62 | flushAndRunServer(1, { signup: { limit: 7 } }), | 73 | flushAndRunServer(1, overrideConfig), |
63 | flushAndRunServer(2) | 74 | flushAndRunServer(2) |
64 | ]) | 75 | ]) |
65 | 76 | ||
@@ -120,7 +131,7 @@ describe('Test users API validators', function () { | |||
120 | 131 | ||
121 | { | 132 | { |
122 | const res = await getMyUserInformation(server.url, server.accessToken) | 133 | const res = await getMyUserInformation(server.url, server.accessToken) |
123 | channelId = res.body.videoChannels[ 0 ].id | 134 | channelId = res.body.videoChannels[0].id |
124 | } | 135 | } |
125 | 136 | ||
126 | { | 137 | { |
@@ -228,6 +239,40 @@ describe('Test users API validators', function () { | |||
228 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | 239 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) |
229 | }) | 240 | }) |
230 | 241 | ||
242 | it('Should fail with empty password and no smtp configured', async function () { | ||
243 | const fields = immutableAssign(baseCorrectParams, { password: '' }) | ||
244 | |||
245 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
246 | }) | ||
247 | |||
248 | it('Should succeed with no password on a server with smtp enabled', async function () { | ||
249 | this.timeout(10000) | ||
250 | |||
251 | killallServers([ server ]) | ||
252 | |||
253 | const config = immutableAssign(overrideConfig, { | ||
254 | smtp: { | ||
255 | hostname: 'localhost', | ||
256 | port: emailPort | ||
257 | } | ||
258 | }) | ||
259 | await reRunServer(server, config) | ||
260 | |||
261 | const fields = immutableAssign(baseCorrectParams, { | ||
262 | password: '', | ||
263 | username: 'create_password', | ||
264 | email: 'create_password@example.com' | ||
265 | }) | ||
266 | |||
267 | await makePostBodyRequest({ | ||
268 | url: server.url, | ||
269 | path: path, | ||
270 | token: server.accessToken, | ||
271 | fields, | ||
272 | statusCodeExpected: 200 | ||
273 | }) | ||
274 | }) | ||
275 | |||
231 | it('Should fail with invalid admin flags', async function () { | 276 | it('Should fail with invalid admin flags', async function () { |
232 | const fields = immutableAssign(baseCorrectParams, { adminFlags: 'toto' }) | 277 | const fields = immutableAssign(baseCorrectParams, { adminFlags: 'toto' }) |
233 | 278 | ||
@@ -529,7 +574,7 @@ describe('Test users API validators', function () { | |||
529 | it('Should fail without an incorrect input file', async function () { | 574 | it('Should fail without an incorrect input file', async function () { |
530 | const fields = {} | 575 | const fields = {} |
531 | const attaches = { | 576 | const attaches = { |
532 | 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') | 577 | avatarfile: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') |
533 | } | 578 | } |
534 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) | 579 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) |
535 | }) | 580 | }) |
@@ -537,7 +582,7 @@ describe('Test users API validators', function () { | |||
537 | it('Should fail with a big file', async function () { | 582 | it('Should fail with a big file', async function () { |
538 | const fields = {} | 583 | const fields = {} |
539 | const attaches = { | 584 | const attaches = { |
540 | 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') | 585 | avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') |
541 | } | 586 | } |
542 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) | 587 | await makeUploadRequest({ url: server.url, path: path + '/me/avatar/pick', token: server.accessToken, fields, attaches }) |
543 | }) | 588 | }) |
@@ -545,7 +590,7 @@ describe('Test users API validators', function () { | |||
545 | it('Should fail with an unauthenticated user', async function () { | 590 | it('Should fail with an unauthenticated user', async function () { |
546 | const fields = {} | 591 | const fields = {} |
547 | const attaches = { | 592 | const attaches = { |
548 | 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') | 593 | avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png') |
549 | } | 594 | } |
550 | await makeUploadRequest({ | 595 | await makeUploadRequest({ |
551 | url: server.url, | 596 | url: server.url, |
@@ -559,7 +604,7 @@ describe('Test users API validators', function () { | |||
559 | it('Should succeed with the correct params', async function () { | 604 | it('Should succeed with the correct params', async function () { |
560 | const fields = {} | 605 | const fields = {} |
561 | const attaches = { | 606 | const attaches = { |
562 | 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') | 607 | avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png') |
563 | } | 608 | } |
564 | await makeUploadRequest({ | 609 | await makeUploadRequest({ |
565 | url: server.url, | 610 | url: server.url, |
@@ -1101,6 +1146,8 @@ describe('Test users API validators', function () { | |||
1101 | }) | 1146 | }) |
1102 | 1147 | ||
1103 | after(async function () { | 1148 | after(async function () { |
1149 | MockSmtpServer.Instance.kill() | ||
1150 | |||
1104 | await cleanupTests([ server, serverWithRegistrationDisabled ]) | 1151 | await cleanupTests([ server, serverWithRegistrationDisabled ]) |
1105 | }) | 1152 | }) |
1106 | }) | 1153 | }) |
diff --git a/server/tests/api/check-params/video-abuses.ts b/server/tests/api/check-params/video-abuses.ts index bf29f8d4d..e643cb95e 100644 --- a/server/tests/api/check-params/video-abuses.ts +++ b/server/tests/api/check-params/video-abuses.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
@@ -76,6 +76,22 @@ describe('Test video abuses API validators', function () { | |||
76 | statusCodeExpected: 403 | 76 | statusCodeExpected: 403 |
77 | }) | 77 | }) |
78 | }) | 78 | }) |
79 | |||
80 | it('Should fail with a bad id filter', async function () { | ||
81 | await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { id: 'toto' } }) | ||
82 | }) | ||
83 | |||
84 | it('Should fail with a bad state filter', async function () { | ||
85 | await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { state: 'toto' } }) | ||
86 | }) | ||
87 | |||
88 | it('Should fail with a bad videoIs filter', async function () { | ||
89 | await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { videoIs: 'toto' } }) | ||
90 | }) | ||
91 | |||
92 | it('Should succeed with the correct params', async function () { | ||
93 | await makeGetRequest({ url: server.url, path, token: server.accessToken, query: { id: 13 }, statusCodeExpected: 200 }) | ||
94 | }) | ||
79 | }) | 95 | }) |
80 | 96 | ||
81 | describe('When reporting a video abuse', function () { | 97 | describe('When reporting a video abuse', function () { |
@@ -126,6 +142,7 @@ describe('Test video abuses API validators', function () { | |||
126 | 142 | ||
127 | describe('When updating a video abuse', function () { | 143 | describe('When updating a video abuse', function () { |
128 | const basePath = '/api/v1/videos/' | 144 | const basePath = '/api/v1/videos/' |
145 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
129 | let path: string | 146 | let path: string |
130 | 147 | ||
131 | before(() => { | 148 | before(() => { |
@@ -163,6 +180,7 @@ describe('Test video abuses API validators', function () { | |||
163 | 180 | ||
164 | describe('When deleting a video abuse', function () { | 181 | describe('When deleting a video abuse', function () { |
165 | const basePath = '/api/v1/videos/' | 182 | const basePath = '/api/v1/videos/' |
183 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
166 | let path: string | 184 | let path: string |
167 | 185 | ||
168 | before(() => { | 186 | before(() => { |
diff --git a/server/tests/api/check-params/video-blacklist.ts b/server/tests/api/check-params/video-blacklist.ts index 6466888fb..145f43980 100644 --- a/server/tests/api/check-params/video-blacklist.ts +++ b/server/tests/api/check-params/video-blacklist.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
@@ -7,25 +7,24 @@ import { | |||
7 | createUser, | 7 | createUser, |
8 | doubleFollow, | 8 | doubleFollow, |
9 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
10 | flushTests, | ||
11 | getBlacklistedVideosList, | 10 | getBlacklistedVideosList, |
12 | getVideo, | 11 | getVideo, |
13 | getVideoWithToken, | 12 | getVideoWithToken, |
14 | killallServers, | ||
15 | makePostBodyRequest, | 13 | makePostBodyRequest, |
16 | makePutBodyRequest, | 14 | makePutBodyRequest, |
17 | removeVideoFromBlacklist, | 15 | removeVideoFromBlacklist, |
18 | ServerInfo, | 16 | ServerInfo, |
19 | setAccessTokensToServers, | 17 | setAccessTokensToServers, |
20 | uploadVideo, | 18 | uploadVideo, |
21 | userLogin, waitJobs | 19 | userLogin, |
20 | waitJobs | ||
22 | } from '../../../../shared/extra-utils' | 21 | } from '../../../../shared/extra-utils' |
23 | import { | 22 | import { |
24 | checkBadCountPagination, | 23 | checkBadCountPagination, |
25 | checkBadSortPagination, | 24 | checkBadSortPagination, |
26 | checkBadStartPagination | 25 | checkBadStartPagination |
27 | } from '../../../../shared/extra-utils/requests/check-api-params' | 26 | } from '../../../../shared/extra-utils/requests/check-api-params' |
28 | import { VideoDetails, VideoBlacklistType } from '../../../../shared/models/videos' | 27 | import { VideoBlacklistType, VideoDetails } from '../../../../shared/models/videos' |
29 | import { expect } from 'chai' | 28 | import { expect } from 'chai' |
30 | 29 | ||
31 | describe('Test video blacklist API validators', function () { | 30 | describe('Test video blacklist API validators', function () { |
@@ -48,14 +47,14 @@ describe('Test video blacklist API validators', function () { | |||
48 | { | 47 | { |
49 | const username = 'user1' | 48 | const username = 'user1' |
50 | const password = 'my super password' | 49 | const password = 'my super password' |
51 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password }) | 50 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: username, password: password }) |
52 | userAccessToken1 = await userLogin(servers[0], { username, password }) | 51 | userAccessToken1 = await userLogin(servers[0], { username, password }) |
53 | } | 52 | } |
54 | 53 | ||
55 | { | 54 | { |
56 | const username = 'user2' | 55 | const username = 'user2' |
57 | const password = 'my super password' | 56 | const password = 'my super password' |
58 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password }) | 57 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: username, password: password }) |
59 | userAccessToken2 = await userLogin(servers[0], { username, password }) | 58 | userAccessToken2 = await userLogin(servers[0], { username, password }) |
60 | } | 59 | } |
61 | 60 | ||
@@ -120,7 +119,7 @@ describe('Test video blacklist API validators', function () { | |||
120 | 119 | ||
121 | it('Should succeed with the correct params', async function () { | 120 | it('Should succeed with the correct params', async function () { |
122 | const path = basePath + servers[0].video.uuid + '/blacklist' | 121 | const path = basePath + servers[0].video.uuid + '/blacklist' |
123 | const fields = { } | 122 | const fields = {} |
124 | 123 | ||
125 | await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 204 }) | 124 | await makePostBodyRequest({ url: servers[0].url, path, token: servers[0].accessToken, fields, statusCodeExpected: 204 }) |
126 | }) | 125 | }) |
diff --git a/server/tests/api/check-params/video-captions.ts b/server/tests/api/check-params/video-captions.ts index 6ddc20d69..a5f5c3322 100644 --- a/server/tests/api/check-params/video-captions.ts +++ b/server/tests/api/check-params/video-captions.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { | 4 | import { |
@@ -50,7 +50,7 @@ describe('Test video captions API validator', function () { | |||
50 | describe('When adding video caption', function () { | 50 | describe('When adding video caption', function () { |
51 | const fields = { } | 51 | const fields = { } |
52 | const attaches = { | 52 | const attaches = { |
53 | 'captionfile': join(__dirname, '..', '..', 'fixtures', 'subtitle-good1.vtt') | 53 | captionfile: join(__dirname, '..', '..', 'fixtures', 'subtitle-good1.vtt') |
54 | } | 54 | } |
55 | 55 | ||
56 | it('Should fail without a valid uuid', async function () { | 56 | it('Should fail without a valid uuid', async function () { |
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts index de88298d1..2795ad7d5 100644 --- a/server/tests/api/check-params/video-channels.ts +++ b/server/tests/api/check-params/video-channels.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
@@ -243,7 +243,7 @@ describe('Test video channels API validator', function () { | |||
243 | it('Should fail with an incorrect input file', async function () { | 243 | it('Should fail with an incorrect input file', async function () { |
244 | const fields = {} | 244 | const fields = {} |
245 | const attaches = { | 245 | const attaches = { |
246 | 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') | 246 | avatarfile: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') |
247 | } | 247 | } |
248 | await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches }) | 248 | await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches }) |
249 | }) | 249 | }) |
@@ -251,7 +251,7 @@ describe('Test video channels API validator', function () { | |||
251 | it('Should fail with a big file', async function () { | 251 | it('Should fail with a big file', async function () { |
252 | const fields = {} | 252 | const fields = {} |
253 | const attaches = { | 253 | const attaches = { |
254 | 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') | 254 | avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') |
255 | } | 255 | } |
256 | await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches }) | 256 | await makeUploadRequest({ url: server.url, path: path + '/avatar/pick', token: server.accessToken, fields, attaches }) |
257 | }) | 257 | }) |
@@ -259,7 +259,7 @@ describe('Test video channels API validator', function () { | |||
259 | it('Should fail with an unauthenticated user', async function () { | 259 | it('Should fail with an unauthenticated user', async function () { |
260 | const fields = {} | 260 | const fields = {} |
261 | const attaches = { | 261 | const attaches = { |
262 | 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') | 262 | avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png') |
263 | } | 263 | } |
264 | await makeUploadRequest({ | 264 | await makeUploadRequest({ |
265 | url: server.url, | 265 | url: server.url, |
@@ -273,7 +273,7 @@ describe('Test video channels API validator', function () { | |||
273 | it('Should succeed with the correct params', async function () { | 273 | it('Should succeed with the correct params', async function () { |
274 | const fields = {} | 274 | const fields = {} |
275 | const attaches = { | 275 | const attaches = { |
276 | 'avatarfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') | 276 | avatarfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png') |
277 | } | 277 | } |
278 | await makeUploadRequest({ | 278 | await makeUploadRequest({ |
279 | url: server.url, | 279 | url: server.url, |
@@ -324,7 +324,7 @@ describe('Test video channels API validator', function () { | |||
324 | }) | 324 | }) |
325 | 325 | ||
326 | it('Should fail with an unknown video channel id', async function () { | 326 | it('Should fail with an unknown video channel id', async function () { |
327 | await deleteVideoChannel(server.url, server.accessToken,'super_channel2', 404) | 327 | await deleteVideoChannel(server.url, server.accessToken, 'super_channel2', 404) |
328 | }) | 328 | }) |
329 | 329 | ||
330 | it('Should succeed with the correct parameters', async function () { | 330 | it('Should succeed with the correct parameters', async function () { |
diff --git a/server/tests/api/check-params/video-comments.ts b/server/tests/api/check-params/video-comments.ts index 5cf90bacc..181282ce1 100644 --- a/server/tests/api/check-params/video-comments.ts +++ b/server/tests/api/check-params/video-comments.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -29,6 +29,7 @@ describe('Test video comments API validator', function () { | |||
29 | let server: ServerInfo | 29 | let server: ServerInfo |
30 | let videoUUID: string | 30 | let videoUUID: string |
31 | let userAccessToken: string | 31 | let userAccessToken: string |
32 | let userAccessToken2: string | ||
32 | let commentId: number | 33 | let commentId: number |
33 | 34 | ||
34 | // --------------------------------------------------------------- | 35 | // --------------------------------------------------------------- |
@@ -53,13 +54,16 @@ describe('Test video comments API validator', function () { | |||
53 | } | 54 | } |
54 | 55 | ||
55 | { | 56 | { |
56 | const user = { | 57 | const user = { username: 'user1', password: 'my super password' } |
57 | username: 'user1', | ||
58 | password: 'my super password' | ||
59 | } | ||
60 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) | 58 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
61 | userAccessToken = await userLogin(server, user) | 59 | userAccessToken = await userLogin(server, user) |
62 | } | 60 | } |
61 | |||
62 | { | ||
63 | const user = { username: 'user2', password: 'my super password' } | ||
64 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) | ||
65 | userAccessToken2 = await userLogin(server, user) | ||
66 | } | ||
63 | }) | 67 | }) |
64 | 68 | ||
65 | describe('When listing video comment threads', function () { | 69 | describe('When listing video comment threads', function () { |
@@ -133,7 +137,7 @@ describe('Test video comments API validator', function () { | |||
133 | 137 | ||
134 | it('Should fail with a long comment', async function () { | 138 | it('Should fail with a long comment', async function () { |
135 | const fields = { | 139 | const fields = { |
136 | text: 'h'.repeat(3001) | 140 | text: 'h'.repeat(10001) |
137 | } | 141 | } |
138 | await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields }) | 142 | await makePostBodyRequest({ url: server.url, path: pathThread, token: server.accessToken, fields }) |
139 | }) | 143 | }) |
@@ -176,7 +180,7 @@ describe('Test video comments API validator', function () { | |||
176 | 180 | ||
177 | it('Should fail with a long comment', async function () { | 181 | it('Should fail with a long comment', async function () { |
178 | const fields = { | 182 | const fields = { |
179 | text: 'h'.repeat(3001) | 183 | text: 'h'.repeat(10001) |
180 | } | 184 | } |
181 | await makePostBodyRequest({ url: server.url, path: pathComment, token: server.accessToken, fields }) | 185 | await makePostBodyRequest({ url: server.url, path: pathComment, token: server.accessToken, fields }) |
182 | }) | 186 | }) |
@@ -224,6 +228,40 @@ describe('Test video comments API validator', function () { | |||
224 | await makeDeleteRequest({ url: server.url, path, token: server.accessToken, statusCodeExpected: 404 }) | 228 | await makeDeleteRequest({ url: server.url, path, token: server.accessToken, statusCodeExpected: 404 }) |
225 | }) | 229 | }) |
226 | 230 | ||
231 | it('Should succeed with the same user', async function () { | ||
232 | let commentToDelete: number | ||
233 | |||
234 | { | ||
235 | const res = await addVideoCommentThread(server.url, userAccessToken, videoUUID, 'hello') | ||
236 | commentToDelete = res.body.comment.id | ||
237 | } | ||
238 | |||
239 | const path = '/api/v1/videos/' + videoUUID + '/comments/' + commentToDelete | ||
240 | |||
241 | await makeDeleteRequest({ url: server.url, path, token: userAccessToken2, statusCodeExpected: 403 }) | ||
242 | await makeDeleteRequest({ url: server.url, path, token: userAccessToken, statusCodeExpected: 204 }) | ||
243 | }) | ||
244 | |||
245 | it('Should succeed with the owner of the video', async function () { | ||
246 | let commentToDelete: number | ||
247 | let anotherVideoUUID: string | ||
248 | |||
249 | { | ||
250 | const res = await uploadVideo(server.url, userAccessToken, { name: 'video' }) | ||
251 | anotherVideoUUID = res.body.video.uuid | ||
252 | } | ||
253 | |||
254 | { | ||
255 | const res = await addVideoCommentThread(server.url, server.accessToken, anotherVideoUUID, 'hello') | ||
256 | commentToDelete = res.body.comment.id | ||
257 | } | ||
258 | |||
259 | const path = '/api/v1/videos/' + anotherVideoUUID + '/comments/' + commentToDelete | ||
260 | |||
261 | await makeDeleteRequest({ url: server.url, path, token: userAccessToken2, statusCodeExpected: 403 }) | ||
262 | await makeDeleteRequest({ url: server.url, path, token: userAccessToken, statusCodeExpected: 204 }) | ||
263 | }) | ||
264 | |||
227 | it('Should succeed with the correct parameters', async function () { | 265 | it('Should succeed with the correct parameters', async function () { |
228 | await makeDeleteRequest({ url: server.url, path: pathComment, token: server.accessToken, statusCodeExpected: 204 }) | 266 | await makeDeleteRequest({ url: server.url, path: pathComment, token: server.accessToken, statusCodeExpected: 204 }) |
229 | }) | 267 | }) |
diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts index 231d5cc85..dbea39c48 100644 --- a/server/tests/api/check-params/video-imports.ts +++ b/server/tests/api/check-params/video-imports.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import { omit } from 'lodash' | 3 | import { omit } from 'lodash' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -29,6 +29,7 @@ describe('Test video imports API validator', function () { | |||
29 | const path = '/api/v1/videos/imports' | 29 | const path = '/api/v1/videos/imports' |
30 | let server: ServerInfo | 30 | let server: ServerInfo |
31 | let userAccessToken = '' | 31 | let userAccessToken = '' |
32 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
32 | let accountName: string | 33 | let accountName: string |
33 | let channelId: number | 34 | let channelId: number |
34 | 35 | ||
@@ -48,7 +49,7 @@ describe('Test video imports API validator', function () { | |||
48 | 49 | ||
49 | { | 50 | { |
50 | const res = await getMyUserInformation(server.url, server.accessToken) | 51 | const res = await getMyUserInformation(server.url, server.accessToken) |
51 | channelId = res.body.videoChannels[ 0 ].id | 52 | channelId = res.body.videoChannels[0].id |
52 | accountName = res.body.account.name + '@' + res.body.account.host | 53 | accountName = res.body.account.name + '@' + res.body.account.host |
53 | } | 54 | } |
54 | }) | 55 | }) |
@@ -196,7 +197,7 @@ describe('Test video imports API validator', function () { | |||
196 | it('Should fail with an incorrect thumbnail file', async function () { | 197 | it('Should fail with an incorrect thumbnail file', async function () { |
197 | const fields = baseCorrectParams | 198 | const fields = baseCorrectParams |
198 | const attaches = { | 199 | const attaches = { |
199 | 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') | 200 | thumbnailfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png') |
200 | } | 201 | } |
201 | 202 | ||
202 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) | 203 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) |
@@ -205,7 +206,7 @@ describe('Test video imports API validator', function () { | |||
205 | it('Should fail with a big thumbnail file', async function () { | 206 | it('Should fail with a big thumbnail file', async function () { |
206 | const fields = baseCorrectParams | 207 | const fields = baseCorrectParams |
207 | const attaches = { | 208 | const attaches = { |
208 | 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') | 209 | thumbnailfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') |
209 | } | 210 | } |
210 | 211 | ||
211 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) | 212 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) |
@@ -214,7 +215,7 @@ describe('Test video imports API validator', function () { | |||
214 | it('Should fail with an incorrect preview file', async function () { | 215 | it('Should fail with an incorrect preview file', async function () { |
215 | const fields = baseCorrectParams | 216 | const fields = baseCorrectParams |
216 | const attaches = { | 217 | const attaches = { |
217 | 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') | 218 | previewfile: join(__dirname, '..', '..', 'fixtures', 'avatar.png') |
218 | } | 219 | } |
219 | 220 | ||
220 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) | 221 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) |
@@ -223,7 +224,7 @@ describe('Test video imports API validator', function () { | |||
223 | it('Should fail with a big preview file', async function () { | 224 | it('Should fail with a big preview file', async function () { |
224 | const fields = baseCorrectParams | 225 | const fields = baseCorrectParams |
225 | const attaches = { | 226 | const attaches = { |
226 | 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') | 227 | previewfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') |
227 | } | 228 | } |
228 | 229 | ||
229 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) | 230 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) |
@@ -232,7 +233,7 @@ describe('Test video imports API validator', function () { | |||
232 | it('Should fail with an invalid torrent file', async function () { | 233 | it('Should fail with an invalid torrent file', async function () { |
233 | const fields = omit(baseCorrectParams, 'targetUrl') | 234 | const fields = omit(baseCorrectParams, 'targetUrl') |
234 | const attaches = { | 235 | const attaches = { |
235 | 'torrentfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') | 236 | torrentfile: join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') |
236 | } | 237 | } |
237 | 238 | ||
238 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) | 239 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches }) |
@@ -303,7 +304,7 @@ describe('Test video imports API validator', function () { | |||
303 | 304 | ||
304 | fields = omit(fields, 'magnetUri') | 305 | fields = omit(fields, 'magnetUri') |
305 | const attaches = { | 306 | const attaches = { |
306 | 'torrentfile': join(__dirname, '..', '..', 'fixtures', 'video-720p.torrent') | 307 | torrentfile: join(__dirname, '..', '..', 'fixtures', 'video-720p.torrent') |
307 | } | 308 | } |
308 | 309 | ||
309 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches, statusCodeExpected: 409 }) | 310 | await makeUploadRequest({ url: server.url, path, token: server.accessToken, fields, attaches, statusCodeExpected: 409 }) |
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts index df158f3b1..0410e737a 100644 --- a/server/tests/api/check-params/video-playlists.ts +++ b/server/tests/api/check-params/video-playlists.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { | 4 | import { |
@@ -36,6 +36,7 @@ describe('Test video playlists API validator', function () { | |||
36 | let privatePlaylistUUID: string | 36 | let privatePlaylistUUID: string |
37 | let watchLaterPlaylistId: number | 37 | let watchLaterPlaylistId: number |
38 | let videoId: number | 38 | let videoId: number |
39 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
39 | let videoId2: number | 40 | let videoId2: number |
40 | let playlistElementId: number | 41 | let playlistElementId: number |
41 | 42 | ||
@@ -449,7 +450,7 @@ describe('Test video playlists API validator', function () { | |||
449 | videoId3 = (await uploadVideoAndGetId({ server, videoName: 'video 3' })).id | 450 | videoId3 = (await uploadVideoAndGetId({ server, videoName: 'video 3' })).id |
450 | videoId4 = (await uploadVideoAndGetId({ server, videoName: 'video 4' })).id | 451 | videoId4 = (await uploadVideoAndGetId({ server, videoName: 'video 4' })).id |
451 | 452 | ||
452 | for (let id of [ videoId3, videoId4 ]) { | 453 | for (const id of [ videoId3, videoId4 ]) { |
453 | await addVideoInPlaylist({ | 454 | await addVideoInPlaylist({ |
454 | url: server.url, | 455 | url: server.url, |
455 | token: server.accessToken, | 456 | token: server.accessToken, |
@@ -476,7 +477,7 @@ describe('Test video playlists API validator', function () { | |||
476 | } | 477 | } |
477 | 478 | ||
478 | { | 479 | { |
479 | const params = getBase({}, { playlistId: 42, expectedStatus: 404 }) | 480 | const params = getBase({}, { playlistId: 42, expectedStatus: 404 }) |
480 | await reorderVideosPlaylist(params) | 481 | await reorderVideosPlaylist(params) |
481 | } | 482 | } |
482 | }) | 483 | }) |
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts index 811756745..ec8654db2 100644 --- a/server/tests/api/check-params/videos-filter.ts +++ b/server/tests/api/check-params/videos-filter.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { | 4 | import { |
5 | cleanupTests, | 5 | cleanupTests, |
6 | createUser, | 6 | createUser, |
7 | createVideoPlaylist, | ||
8 | flushAndRunServer, | 7 | flushAndRunServer, |
9 | makeGetRequest, | 8 | makeGetRequest, |
10 | ServerInfo, | 9 | ServerInfo, |
@@ -13,7 +12,6 @@ import { | |||
13 | userLogin | 12 | userLogin |
14 | } from '../../../../shared/extra-utils' | 13 | } from '../../../../shared/extra-utils' |
15 | import { UserRole } from '../../../../shared/models/users' | 14 | import { UserRole } from '../../../../shared/models/users' |
16 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
17 | 15 | ||
18 | async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) { | 16 | async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) { |
19 | const paths = [ | 17 | const paths = [ |
@@ -77,7 +75,7 @@ describe('Test videos filters', function () { | |||
77 | }) | 75 | }) |
78 | 76 | ||
79 | it('Should succeed with a good filter', async function () { | 77 | it('Should succeed with a good filter', async function () { |
80 | await testEndpoints(server, server.accessToken,'local', 200) | 78 | await testEndpoints(server, server.accessToken, 'local', 200) |
81 | }) | 79 | }) |
82 | 80 | ||
83 | it('Should fail to list all-local with a simple user', async function () { | 81 | it('Should fail to list all-local with a simple user', async function () { |
diff --git a/server/tests/api/check-params/videos-history.ts b/server/tests/api/check-params/videos-history.ts index 3739e3fad..941f62654 100644 --- a/server/tests/api/check-params/videos-history.ts +++ b/server/tests/api/check-params/videos-history.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { | 4 | import { |
6 | checkBadCountPagination, | 5 | checkBadCountPagination, |
@@ -15,12 +14,10 @@ import { | |||
15 | uploadVideo | 14 | uploadVideo |
16 | } from '../../../../shared/extra-utils' | 15 | } from '../../../../shared/extra-utils' |
17 | 16 | ||
18 | const expect = chai.expect | ||
19 | |||
20 | describe('Test videos history API validator', function () { | 17 | describe('Test videos history API validator', function () { |
18 | const myHistoryPath = '/api/v1/users/me/history/videos' | ||
19 | const myHistoryRemove = myHistoryPath + '/remove' | ||
21 | let watchingPath: string | 20 | let watchingPath: string |
22 | let myHistoryPath = '/api/v1/users/me/history/videos' | ||
23 | let myHistoryRemove = myHistoryPath + '/remove' | ||
24 | let server: ServerInfo | 21 | let server: ServerInfo |
25 | 22 | ||
26 | // --------------------------------------------------------------- | 23 | // --------------------------------------------------------------- |
diff --git a/server/tests/api/check-params/videos-overviews.ts b/server/tests/api/check-params/videos-overviews.ts new file mode 100644 index 000000000..69d7fc471 --- /dev/null +++ b/server/tests/api/check-params/videos-overviews.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../../shared/extra-utils' | ||
5 | import { getVideosOverview } from '@shared/extra-utils/overviews/overviews' | ||
6 | |||
7 | describe('Test videos overview', function () { | ||
8 | let server: ServerInfo | ||
9 | |||
10 | // --------------------------------------------------------------- | ||
11 | |||
12 | before(async function () { | ||
13 | this.timeout(30000) | ||
14 | |||
15 | server = await flushAndRunServer(1) | ||
16 | }) | ||
17 | |||
18 | describe('When getting videos overview', function () { | ||
19 | |||
20 | it('Should fail with a bad pagination', async function () { | ||
21 | await getVideosOverview(server.url, 0, 400) | ||
22 | await getVideosOverview(server.url, 100, 400) | ||
23 | }) | ||
24 | |||
25 | it('Should succeed with a good pagination', async function () { | ||
26 | await getVideosOverview(server.url, 1) | ||
27 | }) | ||
28 | }) | ||
29 | |||
30 | after(async function () { | ||
31 | await cleanupTests([ server ]) | ||
32 | }) | ||
33 | }) | ||
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index 16ef1c505..0d4665954 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
@@ -56,8 +56,8 @@ describe('Test videos API validator', function () { | |||
56 | 56 | ||
57 | { | 57 | { |
58 | const res = await getMyUserInformation(server.url, server.accessToken) | 58 | const res = await getMyUserInformation(server.url, server.accessToken) |
59 | channelId = res.body.videoChannels[ 0 ].id | 59 | channelId = res.body.videoChannels[0].id |
60 | channelName = res.body.videoChannels[ 0 ].name | 60 | channelName = res.body.videoChannels[0].name |
61 | accountName = res.body.account.name + '@' + res.body.account.host | 61 | accountName = res.body.account.name + '@' + res.body.account.host |
62 | } | 62 | } |
63 | }) | 63 | }) |
@@ -182,7 +182,7 @@ describe('Test videos API validator', function () { | |||
182 | describe('When adding a video', function () { | 182 | describe('When adding a video', function () { |
183 | let baseCorrectParams | 183 | let baseCorrectParams |
184 | const baseCorrectAttaches = { | 184 | const baseCorrectAttaches = { |
185 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.webm') | 185 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.webm') |
186 | } | 186 | } |
187 | 187 | ||
188 | before(function () { | 188 | before(function () { |
@@ -330,7 +330,7 @@ describe('Test videos API validator', function () { | |||
330 | }) | 330 | }) |
331 | 331 | ||
332 | it('Should fail with a bad originally published at attribute', async function () { | 332 | it('Should fail with a bad originally published at attribute', async function () { |
333 | const fields = immutableAssign(baseCorrectParams, { 'originallyPublishedAt': 'toto' }) | 333 | const fields = immutableAssign(baseCorrectParams, { originallyPublishedAt: 'toto' }) |
334 | const attaches = baseCorrectAttaches | 334 | const attaches = baseCorrectAttaches |
335 | 335 | ||
336 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 336 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -345,12 +345,12 @@ describe('Test videos API validator', function () { | |||
345 | it('Should fail with an incorrect input file', async function () { | 345 | it('Should fail with an incorrect input file', async function () { |
346 | const fields = baseCorrectParams | 346 | const fields = baseCorrectParams |
347 | let attaches = { | 347 | let attaches = { |
348 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short_fake.webm') | 348 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short_fake.webm') |
349 | } | 349 | } |
350 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 350 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
351 | 351 | ||
352 | attaches = { | 352 | attaches = { |
353 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mkv') | 353 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mkv') |
354 | } | 354 | } |
355 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 355 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
356 | }) | 356 | }) |
@@ -358,8 +358,8 @@ describe('Test videos API validator', function () { | |||
358 | it('Should fail with an incorrect thumbnail file', async function () { | 358 | it('Should fail with an incorrect thumbnail file', async function () { |
359 | const fields = baseCorrectParams | 359 | const fields = baseCorrectParams |
360 | const attaches = { | 360 | const attaches = { |
361 | 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), | 361 | thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), |
362 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') | 362 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
363 | } | 363 | } |
364 | 364 | ||
365 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 365 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -368,8 +368,8 @@ describe('Test videos API validator', function () { | |||
368 | it('Should fail with a big thumbnail file', async function () { | 368 | it('Should fail with a big thumbnail file', async function () { |
369 | const fields = baseCorrectParams | 369 | const fields = baseCorrectParams |
370 | const attaches = { | 370 | const attaches = { |
371 | 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), | 371 | thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), |
372 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') | 372 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
373 | } | 373 | } |
374 | 374 | ||
375 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 375 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -378,8 +378,8 @@ describe('Test videos API validator', function () { | |||
378 | it('Should fail with an incorrect preview file', async function () { | 378 | it('Should fail with an incorrect preview file', async function () { |
379 | const fields = baseCorrectParams | 379 | const fields = baseCorrectParams |
380 | const attaches = { | 380 | const attaches = { |
381 | 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), | 381 | previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), |
382 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') | 382 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
383 | } | 383 | } |
384 | 384 | ||
385 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 385 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -388,8 +388,8 @@ describe('Test videos API validator', function () { | |||
388 | it('Should fail with a big preview file', async function () { | 388 | it('Should fail with a big preview file', async function () { |
389 | const fields = baseCorrectParams | 389 | const fields = baseCorrectParams |
390 | const attaches = { | 390 | const attaches = { |
391 | 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), | 391 | previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), |
392 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') | 392 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
393 | } | 393 | } |
394 | 394 | ||
395 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 395 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -566,7 +566,7 @@ describe('Test videos API validator', function () { | |||
566 | it('Should fail with an incorrect thumbnail file', async function () { | 566 | it('Should fail with an incorrect thumbnail file', async function () { |
567 | const fields = baseCorrectParams | 567 | const fields = baseCorrectParams |
568 | const attaches = { | 568 | const attaches = { |
569 | 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png') | 569 | thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png') |
570 | } | 570 | } |
571 | 571 | ||
572 | await makeUploadRequest({ | 572 | await makeUploadRequest({ |
@@ -582,7 +582,7 @@ describe('Test videos API validator', function () { | |||
582 | it('Should fail with a big thumbnail file', async function () { | 582 | it('Should fail with a big thumbnail file', async function () { |
583 | const fields = baseCorrectParams | 583 | const fields = baseCorrectParams |
584 | const attaches = { | 584 | const attaches = { |
585 | 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') | 585 | thumbnailfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') |
586 | } | 586 | } |
587 | 587 | ||
588 | await makeUploadRequest({ | 588 | await makeUploadRequest({ |
@@ -598,7 +598,7 @@ describe('Test videos API validator', function () { | |||
598 | it('Should fail with an incorrect preview file', async function () { | 598 | it('Should fail with an incorrect preview file', async function () { |
599 | const fields = baseCorrectParams | 599 | const fields = baseCorrectParams |
600 | const attaches = { | 600 | const attaches = { |
601 | 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png') | 601 | previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar.png') |
602 | } | 602 | } |
603 | 603 | ||
604 | await makeUploadRequest({ | 604 | await makeUploadRequest({ |
@@ -614,7 +614,7 @@ describe('Test videos API validator', function () { | |||
614 | it('Should fail with a big preview file', async function () { | 614 | it('Should fail with a big preview file', async function () { |
615 | const fields = baseCorrectParams | 615 | const fields = baseCorrectParams |
616 | const attaches = { | 616 | const attaches = { |
617 | 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') | 617 | previewfile: join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') |
618 | } | 618 | } |
619 | 619 | ||
620 | await makeUploadRequest({ | 620 | await makeUploadRequest({ |
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts index 15a34f5aa..dfa2234da 100644 --- a/server/tests/api/notifications/user-notifications.ts +++ b/server/tests/api/notifications/user-notifications.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -63,7 +63,7 @@ import { addUserSubscription, removeUserSubscription } from '../../../../shared/ | |||
63 | import { VideoPrivacy } from '../../../../shared/models/videos' | 63 | import { VideoPrivacy } from '../../../../shared/models/videos' |
64 | import { getBadVideoUrl, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' | 64 | import { getBadVideoUrl, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' |
65 | import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' | 65 | import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' |
66 | import * as uuidv4 from 'uuid/v4' | 66 | import { v4 as uuidv4 } from 'uuid' |
67 | import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/extra-utils/users/blocklist' | 67 | import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/extra-utils/users/blocklist' |
68 | import { CustomConfig } from '../../../../shared/models/server' | 68 | import { CustomConfig } from '../../../../shared/models/server' |
69 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 69 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' |
@@ -74,7 +74,7 @@ async function uploadVideoByRemoteAccount (servers: ServerInfo[], additionalPara | |||
74 | const name = 'remote video ' + uuidv4() | 74 | const name = 'remote video ' + uuidv4() |
75 | 75 | ||
76 | const data = Object.assign({ name }, additionalParams) | 76 | const data = Object.assign({ name }, additionalParams) |
77 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, data) | 77 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, data) |
78 | 78 | ||
79 | await waitJobs(servers) | 79 | await waitJobs(servers) |
80 | 80 | ||
@@ -85,7 +85,7 @@ async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParam | |||
85 | const name = 'local video ' + uuidv4() | 85 | const name = 'local video ' + uuidv4() |
86 | 86 | ||
87 | const data = Object.assign({ name }, additionalParams) | 87 | const data = Object.assign({ name }, additionalParams) |
88 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, data) | 88 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, data) |
89 | 89 | ||
90 | await waitJobs(servers) | 90 | await waitJobs(servers) |
91 | 91 | ||
@@ -95,9 +95,9 @@ async function uploadVideoByLocalAccount (servers: ServerInfo[], additionalParam | |||
95 | describe('Test users notifications', function () { | 95 | describe('Test users notifications', function () { |
96 | let servers: ServerInfo[] = [] | 96 | let servers: ServerInfo[] = [] |
97 | let userAccessToken: string | 97 | let userAccessToken: string |
98 | let userNotifications: UserNotification[] = [] | 98 | const userNotifications: UserNotification[] = [] |
99 | let adminNotifications: UserNotification[] = [] | 99 | const adminNotifications: UserNotification[] = [] |
100 | let adminNotificationsServer2: UserNotification[] = [] | 100 | const adminNotificationsServer2: UserNotification[] = [] |
101 | const emails: object[] = [] | 101 | const emails: object[] = [] |
102 | let channelId: number | 102 | let channelId: number |
103 | 103 | ||
@@ -142,8 +142,8 @@ describe('Test users notifications', function () { | |||
142 | password: 'super password' | 142 | password: 'super password' |
143 | } | 143 | } |
144 | await createUser({ | 144 | await createUser({ |
145 | url: servers[ 0 ].url, | 145 | url: servers[0].url, |
146 | accessToken: servers[ 0 ].accessToken, | 146 | accessToken: servers[0].accessToken, |
147 | username: user.username, | 147 | username: user.username, |
148 | password: user.password, | 148 | password: user.password, |
149 | videoQuota: 10 * 1000 * 1000 | 149 | videoQuota: 10 * 1000 * 1000 |
@@ -155,15 +155,15 @@ describe('Test users notifications', function () { | |||
155 | await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings) | 155 | await updateMyNotificationSettings(servers[1].url, servers[1].accessToken, allNotificationSettings) |
156 | 156 | ||
157 | { | 157 | { |
158 | const socket = getUserNotificationSocket(servers[ 0 ].url, userAccessToken) | 158 | const socket = getUserNotificationSocket(servers[0].url, userAccessToken) |
159 | socket.on('new-notification', n => userNotifications.push(n)) | 159 | socket.on('new-notification', n => userNotifications.push(n)) |
160 | } | 160 | } |
161 | { | 161 | { |
162 | const socket = getUserNotificationSocket(servers[ 0 ].url, servers[0].accessToken) | 162 | const socket = getUserNotificationSocket(servers[0].url, servers[0].accessToken) |
163 | socket.on('new-notification', n => adminNotifications.push(n)) | 163 | socket.on('new-notification', n => adminNotifications.push(n)) |
164 | } | 164 | } |
165 | { | 165 | { |
166 | const socket = getUserNotificationSocket(servers[ 1 ].url, servers[1].accessToken) | 166 | const socket = getUserNotificationSocket(servers[1].url, servers[1].accessToken) |
167 | socket.on('new-notification', n => adminNotificationsServer2.push(n)) | 167 | socket.on('new-notification', n => adminNotificationsServer2.push(n)) |
168 | } | 168 | } |
169 | 169 | ||
@@ -190,7 +190,7 @@ describe('Test users notifications', function () { | |||
190 | 190 | ||
191 | await uploadVideoByLocalAccount(servers) | 191 | await uploadVideoByLocalAccount(servers) |
192 | 192 | ||
193 | const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) | 193 | const notification = await getLastNotification(servers[0].url, userAccessToken) |
194 | expect(notification).to.be.undefined | 194 | expect(notification).to.be.undefined |
195 | 195 | ||
196 | expect(emails).to.have.lengthOf(0) | 196 | expect(emails).to.have.lengthOf(0) |
@@ -221,7 +221,7 @@ describe('Test users notifications', function () { | |||
221 | this.timeout(20000) | 221 | this.timeout(20000) |
222 | 222 | ||
223 | // In 2 seconds | 223 | // In 2 seconds |
224 | let updateAt = new Date(new Date().getTime() + 2000) | 224 | const updateAt = new Date(new Date().getTime() + 2000) |
225 | 225 | ||
226 | const data = { | 226 | const data = { |
227 | privacy: VideoPrivacy.PRIVATE, | 227 | privacy: VideoPrivacy.PRIVATE, |
@@ -240,7 +240,7 @@ describe('Test users notifications', function () { | |||
240 | this.timeout(50000) | 240 | this.timeout(50000) |
241 | 241 | ||
242 | // In 2 seconds | 242 | // In 2 seconds |
243 | let updateAt = new Date(new Date().getTime() + 2000) | 243 | const updateAt = new Date(new Date().getTime() + 2000) |
244 | 244 | ||
245 | const data = { | 245 | const data = { |
246 | privacy: VideoPrivacy.PRIVATE, | 246 | privacy: VideoPrivacy.PRIVATE, |
@@ -259,7 +259,7 @@ describe('Test users notifications', function () { | |||
259 | it('Should not send a notification before the video is published', async function () { | 259 | it('Should not send a notification before the video is published', async function () { |
260 | this.timeout(20000) | 260 | this.timeout(20000) |
261 | 261 | ||
262 | let updateAt = new Date(new Date().getTime() + 1000000) | 262 | const updateAt = new Date(new Date().getTime() + 1000000) |
263 | 263 | ||
264 | const data = { | 264 | const data = { |
265 | privacy: VideoPrivacy.PRIVATE, | 265 | privacy: VideoPrivacy.PRIVATE, |
@@ -386,7 +386,7 @@ describe('Test users notifications', function () { | |||
386 | it('Should not send a new comment notification if the account is muted', async function () { | 386 | it('Should not send a new comment notification if the account is muted', async function () { |
387 | this.timeout(10000) | 387 | this.timeout(10000) |
388 | 388 | ||
389 | await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') | 389 | await addAccountToAccountBlocklist(servers[0].url, userAccessToken, 'root') |
390 | 390 | ||
391 | const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) | 391 | const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: 'super video' }) |
392 | const uuid = resVideo.body.video.uuid | 392 | const uuid = resVideo.body.video.uuid |
@@ -397,7 +397,7 @@ describe('Test users notifications', function () { | |||
397 | await wait(500) | 397 | await wait(500) |
398 | await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence') | 398 | await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'absence') |
399 | 399 | ||
400 | await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') | 400 | await removeAccountFromAccountBlocklist(servers[0].url, userAccessToken, 'root') |
401 | }) | 401 | }) |
402 | 402 | ||
403 | it('Should send a new comment notification after a local comment on my video', async function () { | 403 | it('Should send a new comment notification after a local comment on my video', async function () { |
@@ -456,9 +456,9 @@ describe('Test users notifications', function () { | |||
456 | await waitJobs(servers) | 456 | await waitJobs(servers) |
457 | 457 | ||
458 | { | 458 | { |
459 | const resThread = await addVideoCommentThread(servers[ 1 ].url, servers[ 1 ].accessToken, uuid, 'comment') | 459 | const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment') |
460 | const threadId = resThread.body.comment.id | 460 | const threadId = resThread.body.comment.id |
461 | await addVideoCommentReply(servers[ 1 ].url, servers[ 1 ].accessToken, uuid, threadId, 'reply') | 461 | await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, threadId, 'reply') |
462 | } | 462 | } |
463 | 463 | ||
464 | await waitJobs(servers) | 464 | await waitJobs(servers) |
@@ -530,7 +530,7 @@ describe('Test users notifications', function () { | |||
530 | it('Should not send a new mention notification if the account is muted', async function () { | 530 | it('Should not send a new mention notification if the account is muted', async function () { |
531 | this.timeout(10000) | 531 | this.timeout(10000) |
532 | 532 | ||
533 | await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') | 533 | await addAccountToAccountBlocklist(servers[0].url, userAccessToken, 'root') |
534 | 534 | ||
535 | const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) | 535 | const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'super video' }) |
536 | const uuid = resVideo.body.video.uuid | 536 | const uuid = resVideo.body.video.uuid |
@@ -541,7 +541,7 @@ describe('Test users notifications', function () { | |||
541 | await wait(500) | 541 | await wait(500) |
542 | await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence') | 542 | await checkCommentMention(baseParams, uuid, commentId, commentId, 'super root name', 'absence') |
543 | 543 | ||
544 | await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessToken, 'root') | 544 | await removeAccountFromAccountBlocklist(servers[0].url, userAccessToken, 'root') |
545 | }) | 545 | }) |
546 | 546 | ||
547 | it('Should not send a new mention notification if the remote account mention a local account', async function () { | 547 | it('Should not send a new mention notification if the remote account mention a local account', async function () { |
@@ -585,7 +585,7 @@ describe('Test users notifications', function () { | |||
585 | 585 | ||
586 | await waitJobs(servers) | 586 | await waitJobs(servers) |
587 | 587 | ||
588 | const text1 = `hello @user_1@localhost:${servers[ 0 ].port} 1` | 588 | const text1 = `hello @user_1@localhost:${servers[0].port} 1` |
589 | const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1) | 589 | const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, text1) |
590 | const server2ThreadId = resThread.body.comment.id | 590 | const server2ThreadId = resThread.body.comment.id |
591 | 591 | ||
@@ -596,7 +596,7 @@ describe('Test users notifications', function () { | |||
596 | const server1ThreadId = resThread2.body.data[0].id | 596 | const server1ThreadId = resThread2.body.data[0].id |
597 | await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence') | 597 | await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence') |
598 | 598 | ||
599 | const text2 = `@user_1@localhost:${servers[ 0 ].port} hello 2 @root@localhost:${servers[ 0 ].port}` | 599 | const text2 = `@user_1@localhost:${servers[0].port} hello 2 @root@localhost:${servers[0].port}` |
600 | await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2) | 600 | await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text2) |
601 | 601 | ||
602 | await waitJobs(servers) | 602 | await waitJobs(servers) |
@@ -611,7 +611,7 @@ describe('Test users notifications', function () { | |||
611 | }) | 611 | }) |
612 | }) | 612 | }) |
613 | 613 | ||
614 | describe('Video abuse for moderators notification' , function () { | 614 | describe('Video abuse for moderators notification', function () { |
615 | let baseParams: CheckerBaseParams | 615 | let baseParams: CheckerBaseParams |
616 | 616 | ||
617 | before(() => { | 617 | before(() => { |
@@ -722,7 +722,7 @@ describe('Test users notifications', function () { | |||
722 | await uploadVideoByRemoteAccount(servers, { waitTranscoding: false }) | 722 | await uploadVideoByRemoteAccount(servers, { waitTranscoding: false }) |
723 | await waitJobs(servers) | 723 | await waitJobs(servers) |
724 | 724 | ||
725 | const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) | 725 | const notification = await getLastNotification(servers[0].url, userAccessToken) |
726 | if (notification) { | 726 | if (notification) { |
727 | expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED) | 727 | expect(notification.type).to.not.equal(UserNotificationType.MY_VIDEO_PUBLISHED) |
728 | } | 728 | } |
@@ -769,7 +769,7 @@ describe('Test users notifications', function () { | |||
769 | this.timeout(70000) | 769 | this.timeout(70000) |
770 | 770 | ||
771 | // In 2 seconds | 771 | // In 2 seconds |
772 | let updateAt = new Date(new Date().getTime() + 2000) | 772 | const updateAt = new Date(new Date().getTime() + 2000) |
773 | 773 | ||
774 | const data = { | 774 | const data = { |
775 | privacy: VideoPrivacy.PRIVATE, | 775 | privacy: VideoPrivacy.PRIVATE, |
@@ -787,7 +787,7 @@ describe('Test users notifications', function () { | |||
787 | it('Should not send a notification before the video is published', async function () { | 787 | it('Should not send a notification before the video is published', async function () { |
788 | this.timeout(20000) | 788 | this.timeout(20000) |
789 | 789 | ||
790 | let updateAt = new Date(new Date().getTime() + 1000000) | 790 | const updateAt = new Date(new Date().getTime() + 1000000) |
791 | 791 | ||
792 | const data = { | 792 | const data = { |
793 | privacy: VideoPrivacy.PRIVATE, | 793 | privacy: VideoPrivacy.PRIVATE, |
@@ -970,8 +970,8 @@ describe('Test users notifications', function () { | |||
970 | 970 | ||
971 | describe('New actor follow', function () { | 971 | describe('New actor follow', function () { |
972 | let baseParams: CheckerBaseParams | 972 | let baseParams: CheckerBaseParams |
973 | let myChannelName = 'super channel name' | 973 | const myChannelName = 'super channel name' |
974 | let myUserName = 'super user name' | 974 | const myUserName = 'super user name' |
975 | 975 | ||
976 | before(async () => { | 976 | before(async () => { |
977 | baseParams = { | 977 | baseParams = { |
@@ -1024,25 +1024,26 @@ describe('Test users notifications', function () { | |||
1024 | await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port) | 1024 | await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:' + servers[0].port) |
1025 | }) | 1025 | }) |
1026 | 1026 | ||
1027 | it('Should notify when a local account is following one of our channel', async function () { | 1027 | // PeerTube does not support accout -> account follows |
1028 | this.timeout(10000) | 1028 | // it('Should notify when a local account is following one of our channel', async function () { |
1029 | 1029 | // this.timeout(10000) | |
1030 | await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:' + servers[0].port) | 1030 | // |
1031 | 1031 | // await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:' + servers[0].port) | |
1032 | await waitJobs(servers) | 1032 | // |
1033 | 1033 | // await waitJobs(servers) | |
1034 | await checkNewActorFollow(baseParams, 'account', 'root', 'super root name', myUserName, 'presence') | 1034 | // |
1035 | }) | 1035 | // await checkNewActorFollow(baseParams, 'account', 'root', 'super root name', myUserName, 'presence') |
1036 | 1036 | // }) | |
1037 | it('Should notify when a remote account is following one of our channel', async function () { | 1037 | |
1038 | this.timeout(10000) | 1038 | // it('Should notify when a remote account is following one of our channel', async function () { |
1039 | 1039 | // this.timeout(10000) | |
1040 | await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:' + servers[0].port) | 1040 | // |
1041 | 1041 | // await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:' + servers[0].port) | |
1042 | await waitJobs(servers) | 1042 | // |
1043 | 1043 | // await waitJobs(servers) | |
1044 | await checkNewActorFollow(baseParams, 'account', 'root', 'super root 2 name', myUserName, 'presence') | 1044 | // |
1045 | }) | 1045 | // await checkNewActorFollow(baseParams, 'account', 'root', 'super root 2 name', myUserName, 'presence') |
1046 | // }) | ||
1046 | }) | 1047 | }) |
1047 | 1048 | ||
1048 | describe('Video-related notifications when video auto-blacklist is enabled', function () { | 1049 | describe('Video-related notifications when video auto-blacklist is enabled', function () { |
@@ -1143,7 +1144,7 @@ describe('Test users notifications', function () { | |||
1143 | it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () { | 1144 | it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () { |
1144 | this.timeout(20000) | 1145 | this.timeout(20000) |
1145 | 1146 | ||
1146 | let updateAt = new Date(new Date().getTime() + 1000000) | 1147 | const updateAt = new Date(new Date().getTime() + 1000000) |
1147 | 1148 | ||
1148 | const name = 'video with auto-blacklist and future schedule ' + uuidv4() | 1149 | const name = 'video with auto-blacklist and future schedule ' + uuidv4() |
1149 | 1150 | ||
@@ -1176,7 +1177,7 @@ describe('Test users notifications', function () { | |||
1176 | this.timeout(20000) | 1177 | this.timeout(20000) |
1177 | 1178 | ||
1178 | // In 2 seconds | 1179 | // In 2 seconds |
1179 | let updateAt = new Date(new Date().getTime() + 2000) | 1180 | const updateAt = new Date(new Date().getTime() + 2000) |
1180 | 1181 | ||
1181 | const name = 'video with schedule done and still auto-blacklisted ' + uuidv4() | 1182 | const name = 'video with schedule done and still auto-blacklisted ' + uuidv4() |
1182 | 1183 | ||
@@ -1221,26 +1222,26 @@ describe('Test users notifications', function () { | |||
1221 | 1222 | ||
1222 | describe('Mark as read', function () { | 1223 | describe('Mark as read', function () { |
1223 | it('Should mark as read some notifications', async function () { | 1224 | it('Should mark as read some notifications', async function () { |
1224 | const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3) | 1225 | const res = await getUserNotifications(servers[0].url, userAccessToken, 2, 3) |
1225 | const ids = res.body.data.map(n => n.id) | 1226 | const ids = res.body.data.map(n => n.id) |
1226 | 1227 | ||
1227 | await markAsReadNotifications(servers[ 0 ].url, userAccessToken, ids) | 1228 | await markAsReadNotifications(servers[0].url, userAccessToken, ids) |
1228 | }) | 1229 | }) |
1229 | 1230 | ||
1230 | it('Should have the notifications marked as read', async function () { | 1231 | it('Should have the notifications marked as read', async function () { |
1231 | const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10) | 1232 | const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10) |
1232 | 1233 | ||
1233 | const notifications = res.body.data as UserNotification[] | 1234 | const notifications = res.body.data as UserNotification[] |
1234 | expect(notifications[ 0 ].read).to.be.false | 1235 | expect(notifications[0].read).to.be.false |
1235 | expect(notifications[ 1 ].read).to.be.false | 1236 | expect(notifications[1].read).to.be.false |
1236 | expect(notifications[ 2 ].read).to.be.true | 1237 | expect(notifications[2].read).to.be.true |
1237 | expect(notifications[ 3 ].read).to.be.true | 1238 | expect(notifications[3].read).to.be.true |
1238 | expect(notifications[ 4 ].read).to.be.true | 1239 | expect(notifications[4].read).to.be.true |
1239 | expect(notifications[ 5 ].read).to.be.false | 1240 | expect(notifications[5].read).to.be.false |
1240 | }) | 1241 | }) |
1241 | 1242 | ||
1242 | it('Should only list read notifications', async function () { | 1243 | it('Should only list read notifications', async function () { |
1243 | const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, false) | 1244 | const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, false) |
1244 | 1245 | ||
1245 | const notifications = res.body.data as UserNotification[] | 1246 | const notifications = res.body.data as UserNotification[] |
1246 | for (const notification of notifications) { | 1247 | for (const notification of notifications) { |
@@ -1249,7 +1250,7 @@ describe('Test users notifications', function () { | |||
1249 | }) | 1250 | }) |
1250 | 1251 | ||
1251 | it('Should only list unread notifications', async function () { | 1252 | it('Should only list unread notifications', async function () { |
1252 | const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true) | 1253 | const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, true) |
1253 | 1254 | ||
1254 | const notifications = res.body.data as UserNotification[] | 1255 | const notifications = res.body.data as UserNotification[] |
1255 | for (const notification of notifications) { | 1256 | for (const notification of notifications) { |
@@ -1258,9 +1259,9 @@ describe('Test users notifications', function () { | |||
1258 | }) | 1259 | }) |
1259 | 1260 | ||
1260 | it('Should mark as read all notifications', async function () { | 1261 | it('Should mark as read all notifications', async function () { |
1261 | await markAsReadAllNotifications(servers[ 0 ].url, userAccessToken) | 1262 | await markAsReadAllNotifications(servers[0].url, userAccessToken) |
1262 | 1263 | ||
1263 | const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 0, 10, true) | 1264 | const res = await getUserNotifications(servers[0].url, userAccessToken, 0, 10, true) |
1264 | 1265 | ||
1265 | expect(res.body.total).to.equal(0) | 1266 | expect(res.body.total).to.equal(0) |
1266 | expect(res.body.data).to.have.lengthOf(0) | 1267 | expect(res.body.data).to.have.lengthOf(0) |
diff --git a/server/tests/api/redundancy/index.ts b/server/tests/api/redundancy/index.ts index 8e69b95a6..37dc3f88c 100644 --- a/server/tests/api/redundancy/index.ts +++ b/server/tests/api/redundancy/index.ts | |||
@@ -1 +1,3 @@ | |||
1 | import './redundancy-constraints' | ||
1 | import './redundancy' | 2 | import './redundancy' |
3 | import './manage-redundancy' | ||
diff --git a/server/tests/api/redundancy/manage-redundancy.ts b/server/tests/api/redundancy/manage-redundancy.ts new file mode 100644 index 000000000..4253124c8 --- /dev/null +++ b/server/tests/api/redundancy/manage-redundancy.ts | |||
@@ -0,0 +1,373 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | doubleFollow, | ||
8 | flushAndRunMultipleServers, | ||
9 | getLocalIdByUUID, | ||
10 | ServerInfo, | ||
11 | setAccessTokensToServers, | ||
12 | uploadVideo, | ||
13 | uploadVideoAndGetId, | ||
14 | waitUntilLog | ||
15 | } from '../../../../shared/extra-utils' | ||
16 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
17 | import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy, updateRedundancy } from '@shared/extra-utils/server/redundancy' | ||
18 | import { VideoPrivacy, VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' | ||
19 | |||
20 | const expect = chai.expect | ||
21 | |||
22 | describe('Test manage videos redundancy', function () { | ||
23 | const targets: VideoRedundanciesTarget[] = [ 'my-videos', 'remote-videos' ] | ||
24 | |||
25 | let servers: ServerInfo[] | ||
26 | let video1Server2UUID: string | ||
27 | let video2Server2UUID: string | ||
28 | let redundanciesToRemove: number[] = [] | ||
29 | |||
30 | before(async function () { | ||
31 | this.timeout(120000) | ||
32 | |||
33 | const config = { | ||
34 | transcoding: { | ||
35 | hls: { | ||
36 | enabled: true | ||
37 | } | ||
38 | }, | ||
39 | redundancy: { | ||
40 | videos: { | ||
41 | check_interval: '1 second', | ||
42 | strategies: [ | ||
43 | { | ||
44 | strategy: 'recently-added', | ||
45 | min_lifetime: '1 hour', | ||
46 | size: '10MB', | ||
47 | min_views: 0 | ||
48 | } | ||
49 | ] | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | servers = await flushAndRunMultipleServers(3, config) | ||
54 | |||
55 | // Get the access tokens | ||
56 | await setAccessTokensToServers(servers) | ||
57 | |||
58 | { | ||
59 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 server 2' }) | ||
60 | video1Server2UUID = res.body.video.uuid | ||
61 | } | ||
62 | |||
63 | { | ||
64 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 2 server 2' }) | ||
65 | video2Server2UUID = res.body.video.uuid | ||
66 | } | ||
67 | |||
68 | await waitJobs(servers) | ||
69 | |||
70 | // Server 1 and server 2 follow each other | ||
71 | await doubleFollow(servers[0], servers[1]) | ||
72 | await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, true) | ||
73 | |||
74 | await waitJobs(servers) | ||
75 | }) | ||
76 | |||
77 | it('Should not have redundancies on server 3', async function () { | ||
78 | for (const target of targets) { | ||
79 | const res = await listVideoRedundancies({ | ||
80 | url: servers[2].url, | ||
81 | accessToken: servers[2].accessToken, | ||
82 | target | ||
83 | }) | ||
84 | |||
85 | expect(res.body.total).to.equal(0) | ||
86 | expect(res.body.data).to.have.lengthOf(0) | ||
87 | } | ||
88 | }) | ||
89 | |||
90 | it('Should not have "remote-videos" redundancies on server 2', async function () { | ||
91 | this.timeout(120000) | ||
92 | |||
93 | await waitJobs(servers) | ||
94 | await waitUntilLog(servers[0], 'Duplicated ', 10) | ||
95 | await waitJobs(servers) | ||
96 | |||
97 | const res = await listVideoRedundancies({ | ||
98 | url: servers[1].url, | ||
99 | accessToken: servers[1].accessToken, | ||
100 | target: 'remote-videos' | ||
101 | }) | ||
102 | |||
103 | expect(res.body.total).to.equal(0) | ||
104 | expect(res.body.data).to.have.lengthOf(0) | ||
105 | }) | ||
106 | |||
107 | it('Should have "my-videos" redundancies on server 2', async function () { | ||
108 | this.timeout(120000) | ||
109 | |||
110 | const res = await listVideoRedundancies({ | ||
111 | url: servers[1].url, | ||
112 | accessToken: servers[1].accessToken, | ||
113 | target: 'my-videos' | ||
114 | }) | ||
115 | |||
116 | expect(res.body.total).to.equal(2) | ||
117 | |||
118 | const videos = res.body.data as VideoRedundancy[] | ||
119 | expect(videos).to.have.lengthOf(2) | ||
120 | |||
121 | const videos1 = videos.find(v => v.uuid === video1Server2UUID) | ||
122 | const videos2 = videos.find(v => v.uuid === video2Server2UUID) | ||
123 | |||
124 | expect(videos1.name).to.equal('video 1 server 2') | ||
125 | expect(videos2.name).to.equal('video 2 server 2') | ||
126 | |||
127 | expect(videos1.redundancies.files).to.have.lengthOf(4) | ||
128 | expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1) | ||
129 | |||
130 | const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists) | ||
131 | |||
132 | for (const r of redundancies) { | ||
133 | expect(r.strategy).to.be.null | ||
134 | expect(r.fileUrl).to.exist | ||
135 | expect(r.createdAt).to.exist | ||
136 | expect(r.updatedAt).to.exist | ||
137 | expect(r.expiresOn).to.exist | ||
138 | } | ||
139 | }) | ||
140 | |||
141 | it('Should not have "my-videos" redundancies on server 1', async function () { | ||
142 | const res = await listVideoRedundancies({ | ||
143 | url: servers[0].url, | ||
144 | accessToken: servers[0].accessToken, | ||
145 | target: 'my-videos' | ||
146 | }) | ||
147 | |||
148 | expect(res.body.total).to.equal(0) | ||
149 | expect(res.body.data).to.have.lengthOf(0) | ||
150 | }) | ||
151 | |||
152 | it('Should have "remote-videos" redundancies on server 1', async function () { | ||
153 | this.timeout(120000) | ||
154 | |||
155 | const res = await listVideoRedundancies({ | ||
156 | url: servers[0].url, | ||
157 | accessToken: servers[0].accessToken, | ||
158 | target: 'remote-videos' | ||
159 | }) | ||
160 | |||
161 | expect(res.body.total).to.equal(2) | ||
162 | |||
163 | const videos = res.body.data as VideoRedundancy[] | ||
164 | expect(videos).to.have.lengthOf(2) | ||
165 | |||
166 | const videos1 = videos.find(v => v.uuid === video1Server2UUID) | ||
167 | const videos2 = videos.find(v => v.uuid === video2Server2UUID) | ||
168 | |||
169 | expect(videos1.name).to.equal('video 1 server 2') | ||
170 | expect(videos2.name).to.equal('video 2 server 2') | ||
171 | |||
172 | expect(videos1.redundancies.files).to.have.lengthOf(4) | ||
173 | expect(videos1.redundancies.streamingPlaylists).to.have.lengthOf(1) | ||
174 | |||
175 | const redundancies = videos1.redundancies.files.concat(videos1.redundancies.streamingPlaylists) | ||
176 | |||
177 | for (const r of redundancies) { | ||
178 | expect(r.strategy).to.equal('recently-added') | ||
179 | expect(r.fileUrl).to.exist | ||
180 | expect(r.createdAt).to.exist | ||
181 | expect(r.updatedAt).to.exist | ||
182 | expect(r.expiresOn).to.exist | ||
183 | } | ||
184 | }) | ||
185 | |||
186 | it('Should correctly paginate and sort results', async function () { | ||
187 | { | ||
188 | const res = await listVideoRedundancies({ | ||
189 | url: servers[0].url, | ||
190 | accessToken: servers[0].accessToken, | ||
191 | target: 'remote-videos', | ||
192 | sort: 'name', | ||
193 | start: 0, | ||
194 | count: 2 | ||
195 | }) | ||
196 | |||
197 | const videos = res.body.data | ||
198 | expect(videos[0].name).to.equal('video 1 server 2') | ||
199 | expect(videos[1].name).to.equal('video 2 server 2') | ||
200 | } | ||
201 | |||
202 | { | ||
203 | const res = await listVideoRedundancies({ | ||
204 | url: servers[0].url, | ||
205 | accessToken: servers[0].accessToken, | ||
206 | target: 'remote-videos', | ||
207 | sort: '-name', | ||
208 | start: 0, | ||
209 | count: 2 | ||
210 | }) | ||
211 | |||
212 | const videos = res.body.data | ||
213 | expect(videos[0].name).to.equal('video 2 server 2') | ||
214 | expect(videos[1].name).to.equal('video 1 server 2') | ||
215 | } | ||
216 | |||
217 | { | ||
218 | const res = await listVideoRedundancies({ | ||
219 | url: servers[0].url, | ||
220 | accessToken: servers[0].accessToken, | ||
221 | target: 'remote-videos', | ||
222 | sort: '-name', | ||
223 | start: 1, | ||
224 | count: 1 | ||
225 | }) | ||
226 | |||
227 | const videos = res.body.data | ||
228 | expect(videos[0].name).to.equal('video 1 server 2') | ||
229 | } | ||
230 | }) | ||
231 | |||
232 | it('Should manually add a redundancy and list it', async function () { | ||
233 | this.timeout(120000) | ||
234 | |||
235 | const uuid = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video 3 server 2', privacy: VideoPrivacy.UNLISTED })).uuid | ||
236 | await waitJobs(servers) | ||
237 | const videoId = await getLocalIdByUUID(servers[0].url, uuid) | ||
238 | |||
239 | await addVideoRedundancy({ | ||
240 | url: servers[0].url, | ||
241 | accessToken: servers[0].accessToken, | ||
242 | videoId | ||
243 | }) | ||
244 | |||
245 | await waitJobs(servers) | ||
246 | await waitUntilLog(servers[0], 'Duplicated ', 15) | ||
247 | await waitJobs(servers) | ||
248 | |||
249 | { | ||
250 | const res = await listVideoRedundancies({ | ||
251 | url: servers[0].url, | ||
252 | accessToken: servers[0].accessToken, | ||
253 | target: 'remote-videos', | ||
254 | sort: '-name', | ||
255 | start: 0, | ||
256 | count: 5 | ||
257 | }) | ||
258 | |||
259 | const videos = res.body.data | ||
260 | expect(videos[0].name).to.equal('video 3 server 2') | ||
261 | |||
262 | const video = videos[0] | ||
263 | expect(video.redundancies.files).to.have.lengthOf(4) | ||
264 | expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1) | ||
265 | |||
266 | const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists) | ||
267 | |||
268 | for (const r of redundancies) { | ||
269 | redundanciesToRemove.push(r.id) | ||
270 | |||
271 | expect(r.strategy).to.equal('manual') | ||
272 | expect(r.fileUrl).to.exist | ||
273 | expect(r.createdAt).to.exist | ||
274 | expect(r.updatedAt).to.exist | ||
275 | expect(r.expiresOn).to.be.null | ||
276 | } | ||
277 | } | ||
278 | |||
279 | const res = await listVideoRedundancies({ | ||
280 | url: servers[1].url, | ||
281 | accessToken: servers[1].accessToken, | ||
282 | target: 'my-videos', | ||
283 | sort: '-name', | ||
284 | start: 0, | ||
285 | count: 5 | ||
286 | }) | ||
287 | |||
288 | const videos = res.body.data | ||
289 | expect(videos[0].name).to.equal('video 3 server 2') | ||
290 | |||
291 | const video = videos[0] | ||
292 | expect(video.redundancies.files).to.have.lengthOf(4) | ||
293 | expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1) | ||
294 | |||
295 | const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists) | ||
296 | |||
297 | for (const r of redundancies) { | ||
298 | expect(r.strategy).to.be.null | ||
299 | expect(r.fileUrl).to.exist | ||
300 | expect(r.createdAt).to.exist | ||
301 | expect(r.updatedAt).to.exist | ||
302 | expect(r.expiresOn).to.be.null | ||
303 | } | ||
304 | }) | ||
305 | |||
306 | it('Should manually remove a redundancy and remove it from the list', async function () { | ||
307 | this.timeout(120000) | ||
308 | |||
309 | for (const redundancyId of redundanciesToRemove) { | ||
310 | await removeVideoRedundancy({ | ||
311 | url: servers[0].url, | ||
312 | accessToken: servers[0].accessToken, | ||
313 | redundancyId | ||
314 | }) | ||
315 | } | ||
316 | |||
317 | { | ||
318 | const res = await listVideoRedundancies({ | ||
319 | url: servers[0].url, | ||
320 | accessToken: servers[0].accessToken, | ||
321 | target: 'remote-videos', | ||
322 | sort: '-name', | ||
323 | start: 0, | ||
324 | count: 5 | ||
325 | }) | ||
326 | |||
327 | const videos = res.body.data | ||
328 | expect(videos).to.have.lengthOf(2) | ||
329 | |||
330 | expect(videos[0].name).to.equal('video 2 server 2') | ||
331 | |||
332 | redundanciesToRemove = [] | ||
333 | const video = videos[0] | ||
334 | expect(video.redundancies.files).to.have.lengthOf(4) | ||
335 | expect(video.redundancies.streamingPlaylists).to.have.lengthOf(1) | ||
336 | |||
337 | const redundancies = video.redundancies.files.concat(video.redundancies.streamingPlaylists) | ||
338 | |||
339 | for (const r of redundancies) { | ||
340 | redundanciesToRemove.push(r.id) | ||
341 | } | ||
342 | } | ||
343 | }) | ||
344 | |||
345 | it('Should remove another (auto) redundancy', async function () { | ||
346 | { | ||
347 | for (const redundancyId of redundanciesToRemove) { | ||
348 | await removeVideoRedundancy({ | ||
349 | url: servers[0].url, | ||
350 | accessToken: servers[0].accessToken, | ||
351 | redundancyId | ||
352 | }) | ||
353 | } | ||
354 | |||
355 | const res = await listVideoRedundancies({ | ||
356 | url: servers[0].url, | ||
357 | accessToken: servers[0].accessToken, | ||
358 | target: 'remote-videos', | ||
359 | sort: '-name', | ||
360 | start: 0, | ||
361 | count: 5 | ||
362 | }) | ||
363 | |||
364 | const videos = res.body.data | ||
365 | expect(videos[0].name).to.equal('video 1 server 2') | ||
366 | expect(videos).to.have.lengthOf(1) | ||
367 | } | ||
368 | }) | ||
369 | |||
370 | after(async function () { | ||
371 | await cleanupTests(servers) | ||
372 | }) | ||
373 | }) | ||
diff --git a/server/tests/api/redundancy/redundancy-constraints.ts b/server/tests/api/redundancy/redundancy-constraints.ts new file mode 100644 index 000000000..4fd8f065c --- /dev/null +++ b/server/tests/api/redundancy/redundancy-constraints.ts | |||
@@ -0,0 +1,200 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | cleanupTests, | ||
7 | flushAndRunServer, | ||
8 | follow, | ||
9 | killallServers, | ||
10 | reRunServer, | ||
11 | ServerInfo, | ||
12 | setAccessTokensToServers, | ||
13 | uploadVideo, | ||
14 | waitUntilLog | ||
15 | } from '../../../../shared/extra-utils' | ||
16 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
17 | import { listVideoRedundancies, updateRedundancy } from '@shared/extra-utils/server/redundancy' | ||
18 | |||
19 | const expect = chai.expect | ||
20 | |||
21 | describe('Test redundancy constraints', function () { | ||
22 | let remoteServer: ServerInfo | ||
23 | let localServer: ServerInfo | ||
24 | let servers: ServerInfo[] | ||
25 | |||
26 | async function getTotalRedundanciesLocalServer () { | ||
27 | const res = await listVideoRedundancies({ | ||
28 | url: localServer.url, | ||
29 | accessToken: localServer.accessToken, | ||
30 | target: 'my-videos' | ||
31 | }) | ||
32 | |||
33 | return res.body.total | ||
34 | } | ||
35 | |||
36 | async function getTotalRedundanciesRemoteServer () { | ||
37 | const res = await listVideoRedundancies({ | ||
38 | url: remoteServer.url, | ||
39 | accessToken: remoteServer.accessToken, | ||
40 | target: 'remote-videos' | ||
41 | }) | ||
42 | |||
43 | return res.body.total | ||
44 | } | ||
45 | |||
46 | before(async function () { | ||
47 | this.timeout(120000) | ||
48 | |||
49 | { | ||
50 | const config = { | ||
51 | redundancy: { | ||
52 | videos: { | ||
53 | check_interval: '1 second', | ||
54 | strategies: [ | ||
55 | { | ||
56 | strategy: 'recently-added', | ||
57 | min_lifetime: '1 hour', | ||
58 | size: '100MB', | ||
59 | min_views: 0 | ||
60 | } | ||
61 | ] | ||
62 | } | ||
63 | } | ||
64 | } | ||
65 | remoteServer = await flushAndRunServer(1, config) | ||
66 | } | ||
67 | |||
68 | { | ||
69 | const config = { | ||
70 | remote_redundancy: { | ||
71 | videos: { | ||
72 | accept_from: 'nobody' | ||
73 | } | ||
74 | } | ||
75 | } | ||
76 | localServer = await flushAndRunServer(2, config) | ||
77 | } | ||
78 | |||
79 | servers = [ remoteServer, localServer ] | ||
80 | |||
81 | // Get the access tokens | ||
82 | await setAccessTokensToServers(servers) | ||
83 | |||
84 | await uploadVideo(localServer.url, localServer.accessToken, { name: 'video 1 server 2' }) | ||
85 | |||
86 | await waitJobs(servers) | ||
87 | |||
88 | // Server 1 and server 2 follow each other | ||
89 | await follow(remoteServer.url, [ localServer.url ], remoteServer.accessToken) | ||
90 | await waitJobs(servers) | ||
91 | await updateRedundancy(remoteServer.url, remoteServer.accessToken, localServer.host, true) | ||
92 | |||
93 | await waitJobs(servers) | ||
94 | }) | ||
95 | |||
96 | it('Should have redundancy on server 1 but not on server 2 with a nobody filter', async function () { | ||
97 | this.timeout(120000) | ||
98 | |||
99 | await waitJobs(servers) | ||
100 | await waitUntilLog(remoteServer, 'Duplicated ', 5) | ||
101 | await waitJobs(servers) | ||
102 | |||
103 | { | ||
104 | const total = await getTotalRedundanciesRemoteServer() | ||
105 | expect(total).to.equal(1) | ||
106 | } | ||
107 | |||
108 | { | ||
109 | const total = await getTotalRedundanciesLocalServer() | ||
110 | expect(total).to.equal(0) | ||
111 | } | ||
112 | }) | ||
113 | |||
114 | it('Should have redundancy on server 1 and on server 2 with an anybody filter', async function () { | ||
115 | this.timeout(120000) | ||
116 | |||
117 | const config = { | ||
118 | remote_redundancy: { | ||
119 | videos: { | ||
120 | accept_from: 'anybody' | ||
121 | } | ||
122 | } | ||
123 | } | ||
124 | await killallServers([ localServer ]) | ||
125 | await reRunServer(localServer, config) | ||
126 | |||
127 | await uploadVideo(localServer.url, localServer.accessToken, { name: 'video 2 server 2' }) | ||
128 | |||
129 | await waitJobs(servers) | ||
130 | await waitUntilLog(remoteServer, 'Duplicated ', 10) | ||
131 | await waitJobs(servers) | ||
132 | |||
133 | { | ||
134 | const total = await getTotalRedundanciesRemoteServer() | ||
135 | expect(total).to.equal(2) | ||
136 | } | ||
137 | |||
138 | { | ||
139 | const total = await getTotalRedundanciesLocalServer() | ||
140 | expect(total).to.equal(1) | ||
141 | } | ||
142 | }) | ||
143 | |||
144 | it('Should have redundancy on server 1 but not on server 2 with a followings filter', async function () { | ||
145 | this.timeout(120000) | ||
146 | |||
147 | const config = { | ||
148 | remote_redundancy: { | ||
149 | videos: { | ||
150 | accept_from: 'followings' | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | await killallServers([ localServer ]) | ||
155 | await reRunServer(localServer, config) | ||
156 | |||
157 | await uploadVideo(localServer.url, localServer.accessToken, { name: 'video 3 server 2' }) | ||
158 | |||
159 | await waitJobs(servers) | ||
160 | await waitUntilLog(remoteServer, 'Duplicated ', 15) | ||
161 | await waitJobs(servers) | ||
162 | |||
163 | { | ||
164 | const total = await getTotalRedundanciesRemoteServer() | ||
165 | expect(total).to.equal(3) | ||
166 | } | ||
167 | |||
168 | { | ||
169 | const total = await getTotalRedundanciesLocalServer() | ||
170 | expect(total).to.equal(1) | ||
171 | } | ||
172 | }) | ||
173 | |||
174 | it('Should have redundancy on server 1 and on server 2 with followings filter now server 2 follows server 1', async function () { | ||
175 | this.timeout(120000) | ||
176 | |||
177 | await follow(localServer.url, [ remoteServer.url ], localServer.accessToken) | ||
178 | await waitJobs(servers) | ||
179 | |||
180 | await uploadVideo(localServer.url, localServer.accessToken, { name: 'video 4 server 2' }) | ||
181 | |||
182 | await waitJobs(servers) | ||
183 | await waitUntilLog(remoteServer, 'Duplicated ', 20) | ||
184 | await waitJobs(servers) | ||
185 | |||
186 | { | ||
187 | const total = await getTotalRedundanciesRemoteServer() | ||
188 | expect(total).to.equal(4) | ||
189 | } | ||
190 | |||
191 | { | ||
192 | const total = await getTotalRedundanciesLocalServer() | ||
193 | expect(total).to.equal(2) | ||
194 | } | ||
195 | }) | ||
196 | |||
197 | after(async function () { | ||
198 | await cleanupTests(servers) | ||
199 | }) | ||
200 | }) | ||
diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts index 1cdf93aa1..c5037a541 100644 --- a/server/tests/api/redundancy/redundancy.ts +++ b/server/tests/api/redundancy/redundancy.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoDetails } from '../../../../shared/models/videos' | 5 | import { VideoDetails } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | checkSegmentHash, | 7 | checkSegmentHash, |
8 | checkVideoFilesWereRemoved, cleanupTests, | 8 | checkVideoFilesWereRemoved, |
9 | cleanupTests, | ||
9 | doubleFollow, | 10 | doubleFollow, |
10 | flushAndRunMultipleServers, | 11 | flushAndRunMultipleServers, |
11 | getFollowingListPaginationAndSort, | 12 | getFollowingListPaginationAndSort, |
@@ -28,11 +29,16 @@ import { | |||
28 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 29 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
29 | 30 | ||
30 | import * as magnetUtil from 'magnet-uri' | 31 | import * as magnetUtil from 'magnet-uri' |
31 | import { updateRedundancy } from '../../../../shared/extra-utils/server/redundancy' | 32 | import { |
33 | addVideoRedundancy, | ||
34 | listVideoRedundancies, | ||
35 | removeVideoRedundancy, | ||
36 | updateRedundancy | ||
37 | } from '../../../../shared/extra-utils/server/redundancy' | ||
32 | import { ActorFollow } from '../../../../shared/models/actors' | 38 | import { ActorFollow } from '../../../../shared/models/actors' |
33 | import { readdir } from 'fs-extra' | 39 | import { readdir } from 'fs-extra' |
34 | import { join } from 'path' | 40 | import { join } from 'path' |
35 | import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy' | 41 | import { VideoRedundancy, VideoRedundancyStrategy, VideoRedundancyStrategyWithManual } from '../../../../shared/models/redundancy' |
36 | import { getStats } from '../../../../shared/extra-utils/server/stats' | 42 | import { getStats } from '../../../../shared/extra-utils/server/stats' |
37 | import { ServerStats } from '../../../../shared/models/server/server-stats.model' | 43 | import { ServerStats } from '../../../../shared/models/server/server-stats.model' |
38 | 44 | ||
@@ -40,6 +46,7 @@ const expect = chai.expect | |||
40 | 46 | ||
41 | let servers: ServerInfo[] = [] | 47 | let servers: ServerInfo[] = [] |
42 | let video1Server2UUID: string | 48 | let video1Server2UUID: string |
49 | let video1Server2Id: number | ||
43 | 50 | ||
44 | function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: number } }, baseWebseeds: string[], server: ServerInfo) { | 51 | function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: number } }, baseWebseeds: string[], server: ServerInfo) { |
45 | const parsed = magnetUtil.decode(file.magnetUri) | 52 | const parsed = magnetUtil.decode(file.magnetUri) |
@@ -52,7 +59,19 @@ function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: numbe | |||
52 | expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length) | 59 | expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length) |
53 | } | 60 | } |
54 | 61 | ||
55 | async function flushAndRunServers (strategy: VideoRedundancyStrategy, additionalParams: any = {}) { | 62 | async function flushAndRunServers (strategy: VideoRedundancyStrategy | null, additionalParams: any = {}) { |
63 | const strategies: any[] = [] | ||
64 | |||
65 | if (strategy !== null) { | ||
66 | strategies.push( | ||
67 | immutableAssign({ | ||
68 | min_lifetime: '1 hour', | ||
69 | strategy: strategy, | ||
70 | size: '400KB' | ||
71 | }, additionalParams) | ||
72 | ) | ||
73 | } | ||
74 | |||
56 | const config = { | 75 | const config = { |
57 | transcoding: { | 76 | transcoding: { |
58 | hls: { | 77 | hls: { |
@@ -62,36 +81,32 @@ async function flushAndRunServers (strategy: VideoRedundancyStrategy, additional | |||
62 | redundancy: { | 81 | redundancy: { |
63 | videos: { | 82 | videos: { |
64 | check_interval: '5 seconds', | 83 | check_interval: '5 seconds', |
65 | strategies: [ | 84 | strategies |
66 | immutableAssign({ | ||
67 | min_lifetime: '1 hour', | ||
68 | strategy: strategy, | ||
69 | size: '400KB' | ||
70 | }, additionalParams) | ||
71 | ] | ||
72 | } | 85 | } |
73 | } | 86 | } |
74 | } | 87 | } |
88 | |||
75 | servers = await flushAndRunMultipleServers(3, config) | 89 | servers = await flushAndRunMultipleServers(3, config) |
76 | 90 | ||
77 | // Get the access tokens | 91 | // Get the access tokens |
78 | await setAccessTokensToServers(servers) | 92 | await setAccessTokensToServers(servers) |
79 | 93 | ||
80 | { | 94 | { |
81 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 server 2' }) | 95 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 server 2' }) |
82 | video1Server2UUID = res.body.video.uuid | 96 | video1Server2UUID = res.body.video.uuid |
97 | video1Server2Id = res.body.video.id | ||
83 | 98 | ||
84 | await viewVideo(servers[ 1 ].url, video1Server2UUID) | 99 | await viewVideo(servers[1].url, video1Server2UUID) |
85 | } | 100 | } |
86 | 101 | ||
87 | await waitJobs(servers) | 102 | await waitJobs(servers) |
88 | 103 | ||
89 | // Server 1 and server 2 follow each other | 104 | // Server 1 and server 2 follow each other |
90 | await doubleFollow(servers[ 0 ], servers[ 1 ]) | 105 | await doubleFollow(servers[0], servers[1]) |
91 | // Server 1 and server 3 follow each other | 106 | // Server 1 and server 3 follow each other |
92 | await doubleFollow(servers[ 0 ], servers[ 2 ]) | 107 | await doubleFollow(servers[0], servers[2]) |
93 | // Server 2 and server 3 follow each other | 108 | // Server 2 and server 3 follow each other |
94 | await doubleFollow(servers[ 1 ], servers[ 2 ]) | 109 | await doubleFollow(servers[1], servers[2]) |
95 | 110 | ||
96 | await waitJobs(servers) | 111 | await waitJobs(servers) |
97 | } | 112 | } |
@@ -100,7 +115,7 @@ async function check1WebSeed (videoUUID?: string) { | |||
100 | if (!videoUUID) videoUUID = video1Server2UUID | 115 | if (!videoUUID) videoUUID = video1Server2UUID |
101 | 116 | ||
102 | const webseeds = [ | 117 | const webseeds = [ |
103 | `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}` | 118 | `http://localhost:${servers[1].port}/static/webseed/${videoUUID}` |
104 | ] | 119 | ] |
105 | 120 | ||
106 | for (const server of servers) { | 121 | for (const server of servers) { |
@@ -118,8 +133,8 @@ async function check2Webseeds (videoUUID?: string) { | |||
118 | if (!videoUUID) videoUUID = video1Server2UUID | 133 | if (!videoUUID) videoUUID = video1Server2UUID |
119 | 134 | ||
120 | const webseeds = [ | 135 | const webseeds = [ |
121 | `http://localhost:${servers[ 0 ].port}/static/redundancy/${videoUUID}`, | 136 | `http://localhost:${servers[0].port}/static/redundancy/${videoUUID}`, |
122 | `http://localhost:${servers[ 1 ].port}/static/webseed/${videoUUID}` | 137 | `http://localhost:${servers[1].port}/static/webseed/${videoUUID}` |
123 | ] | 138 | ] |
124 | 139 | ||
125 | for (const server of servers) { | 140 | for (const server of servers) { |
@@ -216,41 +231,50 @@ async function check1PlaylistRedundancies (videoUUID?: string) { | |||
216 | } | 231 | } |
217 | } | 232 | } |
218 | 233 | ||
219 | async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategy) { | 234 | async function checkStatsGlobal (strategy: VideoRedundancyStrategyWithManual) { |
235 | let totalSize: number = null | ||
236 | let statsLength = 1 | ||
237 | |||
238 | if (strategy !== 'manual') { | ||
239 | totalSize = 409600 | ||
240 | statsLength = 2 | ||
241 | } | ||
242 | |||
220 | const res = await getStats(servers[0].url) | 243 | const res = await getStats(servers[0].url) |
221 | const data: ServerStats = res.body | 244 | const data: ServerStats = res.body |
222 | 245 | ||
223 | expect(data.videosRedundancy).to.have.lengthOf(1) | 246 | expect(data.videosRedundancy).to.have.lengthOf(statsLength) |
224 | const stat = data.videosRedundancy[0] | ||
225 | 247 | ||
248 | const stat = data.videosRedundancy[0] | ||
226 | expect(stat.strategy).to.equal(strategy) | 249 | expect(stat.strategy).to.equal(strategy) |
227 | expect(stat.totalSize).to.equal(409600) | 250 | expect(stat.totalSize).to.equal(totalSize) |
251 | |||
252 | return stat | ||
253 | } | ||
254 | |||
255 | async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategyWithManual) { | ||
256 | const stat = await checkStatsGlobal(strategy) | ||
257 | |||
228 | expect(stat.totalUsed).to.be.at.least(1).and.below(409601) | 258 | expect(stat.totalUsed).to.be.at.least(1).and.below(409601) |
229 | expect(stat.totalVideoFiles).to.equal(4) | 259 | expect(stat.totalVideoFiles).to.equal(4) |
230 | expect(stat.totalVideos).to.equal(1) | 260 | expect(stat.totalVideos).to.equal(1) |
231 | } | 261 | } |
232 | 262 | ||
233 | async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategy) { | 263 | async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategyWithManual) { |
234 | const res = await getStats(servers[0].url) | 264 | const stat = await checkStatsGlobal(strategy) |
235 | const data: ServerStats = res.body | ||
236 | 265 | ||
237 | expect(data.videosRedundancy).to.have.lengthOf(1) | ||
238 | |||
239 | const stat = data.videosRedundancy[0] | ||
240 | expect(stat.strategy).to.equal(strategy) | ||
241 | expect(stat.totalSize).to.equal(409600) | ||
242 | expect(stat.totalUsed).to.equal(0) | 266 | expect(stat.totalUsed).to.equal(0) |
243 | expect(stat.totalVideoFiles).to.equal(0) | 267 | expect(stat.totalVideoFiles).to.equal(0) |
244 | expect(stat.totalVideos).to.equal(0) | 268 | expect(stat.totalVideos).to.equal(0) |
245 | } | 269 | } |
246 | 270 | ||
247 | async function enableRedundancyOnServer1 () { | 271 | async function enableRedundancyOnServer1 () { |
248 | await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, true) | 272 | await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, true) |
249 | 273 | ||
250 | const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: '-createdAt' }) | 274 | const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: '-createdAt' }) |
251 | const follows: ActorFollow[] = res.body.data | 275 | const follows: ActorFollow[] = res.body.data |
252 | const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`) | 276 | const server2 = follows.find(f => f.following.host === `localhost:${servers[1].port}`) |
253 | const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`) | 277 | const server3 = follows.find(f => f.following.host === `localhost:${servers[2].port}`) |
254 | 278 | ||
255 | expect(server3).to.not.be.undefined | 279 | expect(server3).to.not.be.undefined |
256 | expect(server3.following.hostRedundancyAllowed).to.be.false | 280 | expect(server3.following.hostRedundancyAllowed).to.be.false |
@@ -260,12 +284,12 @@ async function enableRedundancyOnServer1 () { | |||
260 | } | 284 | } |
261 | 285 | ||
262 | async function disableRedundancyOnServer1 () { | 286 | async function disableRedundancyOnServer1 () { |
263 | await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, false) | 287 | await updateRedundancy(servers[0].url, servers[0].accessToken, servers[1].host, false) |
264 | 288 | ||
265 | const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: '-createdAt' }) | 289 | const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: '-createdAt' }) |
266 | const follows: ActorFollow[] = res.body.data | 290 | const follows: ActorFollow[] = res.body.data |
267 | const server2 = follows.find(f => f.following.host === `localhost:${servers[ 1 ].port}`) | 291 | const server2 = follows.find(f => f.following.host === `localhost:${servers[1].port}`) |
268 | const server3 = follows.find(f => f.following.host === `localhost:${servers[ 2 ].port}`) | 292 | const server3 = follows.find(f => f.following.host === `localhost:${servers[2].port}`) |
269 | 293 | ||
270 | expect(server3).to.not.be.undefined | 294 | expect(server3).to.not.be.undefined |
271 | expect(server3.following.hostRedundancyAllowed).to.be.false | 295 | expect(server3.following.hostRedundancyAllowed).to.be.false |
@@ -410,8 +434,8 @@ describe('Test videos redundancy', function () { | |||
410 | it('Should view 2 times the first video to have > min_views config', async function () { | 434 | it('Should view 2 times the first video to have > min_views config', async function () { |
411 | this.timeout(80000) | 435 | this.timeout(80000) |
412 | 436 | ||
413 | await viewVideo(servers[ 0 ].url, video1Server2UUID) | 437 | await viewVideo(servers[0].url, video1Server2UUID) |
414 | await viewVideo(servers[ 2 ].url, video1Server2UUID) | 438 | await viewVideo(servers[2].url, video1Server2UUID) |
415 | 439 | ||
416 | await wait(10000) | 440 | await wait(10000) |
417 | await waitJobs(servers) | 441 | await waitJobs(servers) |
@@ -446,6 +470,74 @@ describe('Test videos redundancy', function () { | |||
446 | }) | 470 | }) |
447 | }) | 471 | }) |
448 | 472 | ||
473 | describe('With manual strategy', function () { | ||
474 | before(function () { | ||
475 | this.timeout(120000) | ||
476 | |||
477 | return flushAndRunServers(null) | ||
478 | }) | ||
479 | |||
480 | it('Should have 1 webseed on the first video', async function () { | ||
481 | await check1WebSeed() | ||
482 | await check0PlaylistRedundancies() | ||
483 | await checkStatsWith1Webseed('manual') | ||
484 | }) | ||
485 | |||
486 | it('Should create a redundancy on first video', async function () { | ||
487 | await addVideoRedundancy({ | ||
488 | url: servers[0].url, | ||
489 | accessToken: servers[0].accessToken, | ||
490 | videoId: video1Server2Id | ||
491 | }) | ||
492 | }) | ||
493 | |||
494 | it('Should have 2 webseeds on the first video', async function () { | ||
495 | this.timeout(80000) | ||
496 | |||
497 | await waitJobs(servers) | ||
498 | await waitUntilLog(servers[0], 'Duplicated ', 5) | ||
499 | await waitJobs(servers) | ||
500 | |||
501 | await check2Webseeds() | ||
502 | await check1PlaylistRedundancies() | ||
503 | await checkStatsWith2Webseed('manual') | ||
504 | }) | ||
505 | |||
506 | it('Should manually remove redundancies on server 1 and remove duplicated videos', async function () { | ||
507 | this.timeout(80000) | ||
508 | |||
509 | const res = await listVideoRedundancies({ | ||
510 | url: servers[0].url, | ||
511 | accessToken: servers[0].accessToken, | ||
512 | target: 'remote-videos' | ||
513 | }) | ||
514 | |||
515 | const videos = res.body.data as VideoRedundancy[] | ||
516 | expect(videos).to.have.lengthOf(1) | ||
517 | |||
518 | const video = videos[0] | ||
519 | for (const r of video.redundancies.files.concat(video.redundancies.streamingPlaylists)) { | ||
520 | await removeVideoRedundancy({ | ||
521 | url: servers[0].url, | ||
522 | accessToken: servers[0].accessToken, | ||
523 | redundancyId: r.id | ||
524 | }) | ||
525 | } | ||
526 | |||
527 | await waitJobs(servers) | ||
528 | await wait(5000) | ||
529 | |||
530 | await check1WebSeed() | ||
531 | await check0PlaylistRedundancies() | ||
532 | |||
533 | await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ]) | ||
534 | }) | ||
535 | |||
536 | after(async function () { | ||
537 | await cleanupTests(servers) | ||
538 | }) | ||
539 | }) | ||
540 | |||
449 | describe('Test expiration', function () { | 541 | describe('Test expiration', function () { |
450 | const strategy = 'recently-added' | 542 | const strategy = 'recently-added' |
451 | 543 | ||
@@ -528,7 +620,7 @@ describe('Test videos redundancy', function () { | |||
528 | await check1PlaylistRedundancies() | 620 | await check1PlaylistRedundancies() |
529 | await checkStatsWith2Webseed(strategy) | 621 | await checkStatsWith2Webseed(strategy) |
530 | 622 | ||
531 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 2 server 2' }) | 623 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 2 server 2' }) |
532 | video2Server2UUID = res.body.video.uuid | 624 | video2Server2UUID = res.body.video.uuid |
533 | }) | 625 | }) |
534 | 626 | ||
@@ -560,8 +652,8 @@ describe('Test videos redundancy', function () { | |||
560 | 652 | ||
561 | await waitJobs(servers) | 653 | await waitJobs(servers) |
562 | 654 | ||
563 | killallServers([ servers[ 0 ] ]) | 655 | killallServers([ servers[0] ]) |
564 | await reRunServer(servers[ 0 ], { | 656 | await reRunServer(servers[0], { |
565 | redundancy: { | 657 | redundancy: { |
566 | videos: { | 658 | videos: { |
567 | check_interval: '1 second', | 659 | check_interval: '1 second', |
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts index d5f0a5457..d7e3ed5be 100644 --- a/server/tests/api/search/search-activitypub-video-channels.ts +++ b/server/tests/api/search/search-activitypub-video-channels.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -39,7 +39,7 @@ describe('Test ActivityPub video channels search', function () { | |||
39 | await setAccessTokensToServers(servers) | 39 | await setAccessTokensToServers(servers) |
40 | 40 | ||
41 | { | 41 | { |
42 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'user1_server1', password: 'password' }) | 42 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: 'user1_server1', password: 'password' }) |
43 | const channel = { | 43 | const channel = { |
44 | name: 'channel1_server1', | 44 | name: 'channel1_server1', |
45 | displayName: 'Channel 1 server 1' | 45 | displayName: 'Channel 1 server 1' |
@@ -49,7 +49,7 @@ describe('Test ActivityPub video channels search', function () { | |||
49 | 49 | ||
50 | { | 50 | { |
51 | const user = { username: 'user1_server2', password: 'password' } | 51 | const user = { username: 'user1_server2', password: 'password' } |
52 | await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) | 52 | await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password }) |
53 | userServer2Token = await userLogin(servers[1], user) | 53 | userServer2Token = await userLogin(servers[1], user) |
54 | 54 | ||
55 | const channel = { | 55 | const channel = { |
@@ -70,8 +70,8 @@ describe('Test ActivityPub video channels search', function () { | |||
70 | this.timeout(15000) | 70 | this.timeout(15000) |
71 | 71 | ||
72 | { | 72 | { |
73 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server3' | 73 | const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server3' |
74 | const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) | 74 | const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) |
75 | 75 | ||
76 | expect(res.body.total).to.equal(0) | 76 | expect(res.body.total).to.equal(0) |
77 | expect(res.body.data).to.be.an('array') | 77 | expect(res.body.data).to.be.an('array') |
@@ -80,7 +80,7 @@ describe('Test ActivityPub video channels search', function () { | |||
80 | 80 | ||
81 | { | 81 | { |
82 | // Without token | 82 | // Without token |
83 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' | 83 | const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2' |
84 | const res = await searchVideoChannel(servers[0].url, search) | 84 | const res = await searchVideoChannel(servers[0].url, search) |
85 | 85 | ||
86 | expect(res.body.total).to.equal(0) | 86 | expect(res.body.total).to.equal(0) |
@@ -91,35 +91,35 @@ describe('Test ActivityPub video channels search', function () { | |||
91 | 91 | ||
92 | it('Should search a local video channel', async function () { | 92 | it('Should search a local video channel', async function () { |
93 | const searches = [ | 93 | const searches = [ |
94 | 'http://localhost:' + servers[ 0 ].port + '/video-channels/channel1_server1', | 94 | 'http://localhost:' + servers[0].port + '/video-channels/channel1_server1', |
95 | 'channel1_server1@localhost:' + servers[ 0 ].port | 95 | 'channel1_server1@localhost:' + servers[0].port |
96 | ] | 96 | ] |
97 | 97 | ||
98 | for (const search of searches) { | 98 | for (const search of searches) { |
99 | const res = await searchVideoChannel(servers[ 0 ].url, search) | 99 | const res = await searchVideoChannel(servers[0].url, search) |
100 | 100 | ||
101 | expect(res.body.total).to.equal(1) | 101 | expect(res.body.total).to.equal(1) |
102 | expect(res.body.data).to.be.an('array') | 102 | expect(res.body.data).to.be.an('array') |
103 | expect(res.body.data).to.have.lengthOf(1) | 103 | expect(res.body.data).to.have.lengthOf(1) |
104 | expect(res.body.data[ 0 ].name).to.equal('channel1_server1') | 104 | expect(res.body.data[0].name).to.equal('channel1_server1') |
105 | expect(res.body.data[ 0 ].displayName).to.equal('Channel 1 server 1') | 105 | expect(res.body.data[0].displayName).to.equal('Channel 1 server 1') |
106 | } | 106 | } |
107 | }) | 107 | }) |
108 | 108 | ||
109 | it('Should search a remote video channel with URL or handle', async function () { | 109 | it('Should search a remote video channel with URL or handle', async function () { |
110 | const searches = [ | 110 | const searches = [ |
111 | 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2', | 111 | 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2', |
112 | 'channel1_server2@localhost:' + servers[ 1 ].port | 112 | 'channel1_server2@localhost:' + servers[1].port |
113 | ] | 113 | ] |
114 | 114 | ||
115 | for (const search of searches) { | 115 | for (const search of searches) { |
116 | const res = await searchVideoChannel(servers[ 0 ].url, search, servers[ 0 ].accessToken) | 116 | const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) |
117 | 117 | ||
118 | expect(res.body.total).to.equal(1) | 118 | expect(res.body.total).to.equal(1) |
119 | expect(res.body.data).to.be.an('array') | 119 | expect(res.body.data).to.be.an('array') |
120 | expect(res.body.data).to.have.lengthOf(1) | 120 | expect(res.body.data).to.have.lengthOf(1) |
121 | expect(res.body.data[ 0 ].name).to.equal('channel1_server2') | 121 | expect(res.body.data[0].name).to.equal('channel1_server2') |
122 | expect(res.body.data[ 0 ].displayName).to.equal('Channel 1 server 2') | 122 | expect(res.body.data[0].displayName).to.equal('Channel 1 server 2') |
123 | } | 123 | } |
124 | }) | 124 | }) |
125 | 125 | ||
@@ -137,13 +137,13 @@ describe('Test ActivityPub video channels search', function () { | |||
137 | 137 | ||
138 | await waitJobs(servers) | 138 | await waitJobs(servers) |
139 | 139 | ||
140 | const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5) | 140 | const res = await getVideoChannelVideos(servers[0].url, null, 'channel1_server2@localhost:' + servers[1].port, 0, 5) |
141 | expect(res.body.total).to.equal(0) | 141 | expect(res.body.total).to.equal(0) |
142 | expect(res.body.data).to.have.lengthOf(0) | 142 | expect(res.body.data).to.have.lengthOf(0) |
143 | }) | 143 | }) |
144 | 144 | ||
145 | it('Should list video channel videos of server 2 with token', async function () { | 145 | it('Should list video channel videos of server 2 with token', async function () { |
146 | const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:' + servers[ 1 ].port, 0, 5) | 146 | const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, 'channel1_server2@localhost:' + servers[1].port, 0, 5) |
147 | 147 | ||
148 | expect(res.body.total).to.equal(1) | 148 | expect(res.body.total).to.equal(1) |
149 | expect(res.body.data[0].name).to.equal('video 1 server 2') | 149 | expect(res.body.data[0].name).to.equal('video 1 server 2') |
@@ -159,7 +159,7 @@ describe('Test ActivityPub video channels search', function () { | |||
159 | // Expire video channel | 159 | // Expire video channel |
160 | await wait(10000) | 160 | await wait(10000) |
161 | 161 | ||
162 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' | 162 | const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2' |
163 | const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) | 163 | const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) |
164 | expect(res.body.total).to.equal(1) | 164 | expect(res.body.total).to.equal(1) |
165 | expect(res.body.data).to.have.lengthOf(1) | 165 | expect(res.body.data).to.have.lengthOf(1) |
@@ -182,12 +182,12 @@ describe('Test ActivityPub video channels search', function () { | |||
182 | // Expire video channel | 182 | // Expire video channel |
183 | await wait(10000) | 183 | await wait(10000) |
184 | 184 | ||
185 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' | 185 | const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2' |
186 | await searchVideoChannel(servers[0].url, search, servers[0].accessToken) | 186 | await searchVideoChannel(servers[0].url, search, servers[0].accessToken) |
187 | 187 | ||
188 | await waitJobs(servers) | 188 | await waitJobs(servers) |
189 | 189 | ||
190 | const videoChannelName = 'channel1_server2@localhost:' + servers[ 1 ].port | 190 | const videoChannelName = 'channel1_server2@localhost:' + servers[1].port |
191 | const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, videoChannelName, 0, 5, '-createdAt') | 191 | const res = await getVideoChannelVideos(servers[0].url, servers[0].accessToken, videoChannelName, 0, 5, '-createdAt') |
192 | 192 | ||
193 | expect(res.body.total).to.equal(2) | 193 | expect(res.body.total).to.equal(2) |
@@ -204,7 +204,7 @@ describe('Test ActivityPub video channels search', function () { | |||
204 | // Expire video | 204 | // Expire video |
205 | await wait(10000) | 205 | await wait(10000) |
206 | 206 | ||
207 | const search = 'http://localhost:' + servers[ 1 ].port + '/video-channels/channel1_server2' | 207 | const search = 'http://localhost:' + servers[1].port + '/video-channels/channel1_server2' |
208 | const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) | 208 | const res = await searchVideoChannel(servers[0].url, search, servers[0].accessToken) |
209 | expect(res.body.total).to.equal(0) | 209 | expect(res.body.total).to.equal(0) |
210 | expect(res.body.data).to.have.lengthOf(0) | 210 | expect(res.body.data).to.have.lengthOf(0) |
diff --git a/server/tests/api/search/search-activitypub-videos.ts b/server/tests/api/search/search-activitypub-videos.ts index dbfefadda..c62dfca0d 100644 --- a/server/tests/api/search/search-activitypub-videos.ts +++ b/server/tests/api/search/search-activitypub-videos.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -34,12 +34,12 @@ describe('Test ActivityPub videos search', function () { | |||
34 | await setAccessTokensToServers(servers) | 34 | await setAccessTokensToServers(servers) |
35 | 35 | ||
36 | { | 36 | { |
37 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1 on server 1' }) | 37 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video 1 on server 1' }) |
38 | videoServer1UUID = res.body.video.uuid | 38 | videoServer1UUID = res.body.video.uuid |
39 | } | 39 | } |
40 | 40 | ||
41 | { | 41 | { |
42 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 1 on server 2' }) | 42 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video 1 on server 2' }) |
43 | videoServer2UUID = res.body.video.uuid | 43 | videoServer2UUID = res.body.video.uuid |
44 | } | 44 | } |
45 | 45 | ||
@@ -49,7 +49,7 @@ describe('Test ActivityPub videos search', function () { | |||
49 | it('Should not find a remote video', async function () { | 49 | it('Should not find a remote video', async function () { |
50 | { | 50 | { |
51 | const search = 'http://localhost:' + servers[1].port + '/videos/watch/43' | 51 | const search = 'http://localhost:' + servers[1].port + '/videos/watch/43' |
52 | const res = await searchVideoWithToken(servers[ 0 ].url, search, servers[ 0 ].accessToken) | 52 | const res = await searchVideoWithToken(servers[0].url, search, servers[0].accessToken) |
53 | 53 | ||
54 | expect(res.body.total).to.equal(0) | 54 | expect(res.body.total).to.equal(0) |
55 | expect(res.body.data).to.be.an('array') | 55 | expect(res.body.data).to.be.an('array') |
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts index 7882d9373..4801fe04a 100644 --- a/server/tests/api/search/search-videos.ts +++ b/server/tests/api/search/search-videos.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -78,7 +78,7 @@ describe('Test videos search', function () { | |||
78 | const attributes5 = immutableAssign(attributes1, { name: attributes1.name + ' - 5', licence: 2, language: undefined }) | 78 | const attributes5 = immutableAssign(attributes1, { name: attributes1.name + ' - 5', licence: 2, language: undefined }) |
79 | await uploadVideo(server.url, server.accessToken, attributes5) | 79 | await uploadVideo(server.url, server.accessToken, attributes5) |
80 | 80 | ||
81 | const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2 '] }) | 81 | const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2' ] }) |
82 | await uploadVideo(server.url, server.accessToken, attributes6) | 82 | await uploadVideo(server.url, server.accessToken, attributes6) |
83 | 83 | ||
84 | const attributes7 = immutableAssign(attributes1, { | 84 | const attributes7 = immutableAssign(attributes1, { |
@@ -269,16 +269,16 @@ describe('Test videos search', function () { | |||
269 | { | 269 | { |
270 | const res = await advancedVideosSearch(server.url, query) | 270 | const res = await advancedVideosSearch(server.url, query) |
271 | expect(res.body.total).to.equal(2) | 271 | expect(res.body.total).to.equal(2) |
272 | expect(res.body.data[ 0 ].name).to.equal('1111 2222 3333 - 3') | 272 | expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3') |
273 | expect(res.body.data[ 1 ].name).to.equal('1111 2222 3333 - 4') | 273 | expect(res.body.data[1].name).to.equal('1111 2222 3333 - 4') |
274 | } | 274 | } |
275 | 275 | ||
276 | { | 276 | { |
277 | const res = await advancedVideosSearch(server.url, immutableAssign(query, { languageOneOf: [ 'pl', 'en', '_unknown' ] })) | 277 | const res = await advancedVideosSearch(server.url, immutableAssign(query, { languageOneOf: [ 'pl', 'en', '_unknown' ] })) |
278 | expect(res.body.total).to.equal(3) | 278 | expect(res.body.total).to.equal(3) |
279 | expect(res.body.data[ 0 ].name).to.equal('1111 2222 3333 - 3') | 279 | expect(res.body.data[0].name).to.equal('1111 2222 3333 - 3') |
280 | expect(res.body.data[ 1 ].name).to.equal('1111 2222 3333 - 4') | 280 | expect(res.body.data[1].name).to.equal('1111 2222 3333 - 4') |
281 | expect(res.body.data[ 2 ].name).to.equal('1111 2222 3333 - 5') | 281 | expect(res.body.data[2].name).to.equal('1111 2222 3333 - 5') |
282 | } | 282 | } |
283 | 283 | ||
284 | { | 284 | { |
diff --git a/server/tests/api/server/auto-follows.ts b/server/tests/api/server/auto-follows.ts index a06f578fc..7efccc3e2 100644 --- a/server/tests/api/server/auto-follows.ts +++ b/server/tests/api/server/auto-follows.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -59,9 +59,10 @@ async function server1Follows2 (servers: ServerInfo[]) { | |||
59 | 59 | ||
60 | async function resetFollows (servers: ServerInfo[]) { | 60 | async function resetFollows (servers: ServerInfo[]) { |
61 | try { | 61 | try { |
62 | await unfollow(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ]) | 62 | await unfollow(servers[0].url, servers[0].accessToken, servers[1]) |
63 | await unfollow(servers[ 1 ].url, servers[ 1 ].accessToken, servers[ 0 ]) | 63 | await unfollow(servers[1].url, servers[1].accessToken, servers[0]) |
64 | } catch { /* empty */ } | 64 | } catch { /* empty */ |
65 | } | ||
65 | 66 | ||
66 | await waitJobs(servers) | 67 | await waitJobs(servers) |
67 | 68 | ||
@@ -163,8 +164,8 @@ describe('Test auto follows', function () { | |||
163 | await wait(5000) | 164 | await wait(5000) |
164 | await waitJobs(servers) | 165 | await waitJobs(servers) |
165 | 166 | ||
166 | await checkFollow(servers[ 0 ], servers[ 1 ], false) | 167 | await checkFollow(servers[0], servers[1], false) |
167 | await checkFollow(servers[ 1 ], servers[ 0 ], false) | 168 | await checkFollow(servers[1], servers[0], false) |
168 | }) | 169 | }) |
169 | 170 | ||
170 | it('Should auto follow the index', async function () { | 171 | it('Should auto follow the index', async function () { |
@@ -176,7 +177,7 @@ describe('Test auto follows', function () { | |||
176 | followings: { | 177 | followings: { |
177 | instance: { | 178 | instance: { |
178 | autoFollowIndex: { | 179 | autoFollowIndex: { |
179 | indexUrl: 'http://localhost:42100', | 180 | indexUrl: 'http://localhost:42100/api/v1/instances/hosts', |
180 | enabled: true | 181 | enabled: true |
181 | } | 182 | } |
182 | } | 183 | } |
@@ -187,7 +188,7 @@ describe('Test auto follows', function () { | |||
187 | await wait(5000) | 188 | await wait(5000) |
188 | await waitJobs(servers) | 189 | await waitJobs(servers) |
189 | 190 | ||
190 | await checkFollow(servers[ 0 ], servers[ 1 ], true) | 191 | await checkFollow(servers[0], servers[1], true) |
191 | 192 | ||
192 | await resetFollows(servers) | 193 | await resetFollows(servers) |
193 | }) | 194 | }) |
@@ -200,8 +201,8 @@ describe('Test auto follows', function () { | |||
200 | await wait(5000) | 201 | await wait(5000) |
201 | await waitJobs(servers) | 202 | await waitJobs(servers) |
202 | 203 | ||
203 | await checkFollow(servers[ 0 ], servers[ 1 ], false) | 204 | await checkFollow(servers[0], servers[1], false) |
204 | await checkFollow(servers[ 0 ], servers[ 2 ], true) | 205 | await checkFollow(servers[0], servers[2], true) |
205 | }) | 206 | }) |
206 | }) | 207 | }) |
207 | 208 | ||
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index cf99e5c0a..1d81e1dce 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
@@ -11,11 +11,14 @@ import { | |||
11 | getAbout, | 11 | getAbout, |
12 | getConfig, | 12 | getConfig, |
13 | getCustomConfig, | 13 | getCustomConfig, |
14 | killallServers, parallelTests, | 14 | killallServers, |
15 | parallelTests, | ||
15 | registerUser, | 16 | registerUser, |
16 | reRunServer, ServerInfo, | 17 | reRunServer, |
18 | ServerInfo, | ||
17 | setAccessTokensToServers, | 19 | setAccessTokensToServers, |
18 | updateCustomConfig, uploadVideo | 20 | updateCustomConfig, |
21 | uploadVideo | ||
19 | } from '../../../../shared/extra-utils' | 22 | } from '../../../../shared/extra-utils' |
20 | import { ServerConfig } from '../../../../shared/models' | 23 | import { ServerConfig } from '../../../../shared/models' |
21 | 24 | ||
@@ -24,8 +27,7 @@ const expect = chai.expect | |||
24 | function checkInitialConfig (server: ServerInfo, data: CustomConfig) { | 27 | function checkInitialConfig (server: ServerInfo, data: CustomConfig) { |
25 | expect(data.instance.name).to.equal('PeerTube') | 28 | expect(data.instance.name).to.equal('PeerTube') |
26 | expect(data.instance.shortDescription).to.equal( | 29 | expect(data.instance.shortDescription).to.equal( |
27 | 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser ' + | 30 | 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.' |
28 | 'with WebTorrent and Angular.' | ||
29 | ) | 31 | ) |
30 | expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') | 32 | expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') |
31 | 33 | ||
diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts index e4e895acb..8d1270358 100644 --- a/server/tests/api/server/contact-form.ts +++ b/server/tests/api/server/contact-form.ts | |||
@@ -1,16 +1,8 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { cleanupTests, flushAndRunServer, ServerInfo, setAccessTokensToServers, wait } from '../../../../shared/extra-utils' |
6 | flushTests, | ||
7 | killallServers, | ||
8 | flushAndRunServer, | ||
9 | ServerInfo, | ||
10 | setAccessTokensToServers, | ||
11 | wait, | ||
12 | cleanupTests | ||
13 | } from '../../../../shared/extra-utils' | ||
14 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' | 6 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' |
15 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 7 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
16 | import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' | 8 | import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' |
@@ -54,7 +46,7 @@ describe('Test contact form', function () { | |||
54 | const email = emails[0] | 46 | const email = emails[0] |
55 | 47 | ||
56 | expect(email['from'][0]['address']).equal('test-admin@localhost') | 48 | expect(email['from'][0]['address']).equal('test-admin@localhost') |
57 | expect(email['from'][0]['name']).equal('toto@example.com') | 49 | expect(email['replyTo'][0]['address']).equal('toto@example.com') |
58 | expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com') | 50 | expect(email['to'][0]['address']).equal('admin' + server.internalServerNumber + '@example.com') |
59 | expect(email['subject']).contains('my subject') | 51 | expect(email['subject']).contains('my subject') |
60 | expect(email['text']).contains('my super message') | 52 | expect(email['text']).contains('my super message') |
diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts index c55a221f2..95b64a459 100644 --- a/server/tests/api/server/email.ts +++ b/server/tests/api/server/email.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -28,10 +28,12 @@ const expect = chai.expect | |||
28 | describe('Test emails', function () { | 28 | describe('Test emails', function () { |
29 | let server: ServerInfo | 29 | let server: ServerInfo |
30 | let userId: number | 30 | let userId: number |
31 | let userId2: number | ||
31 | let userAccessToken: string | 32 | let userAccessToken: string |
32 | let videoUUID: string | 33 | let videoUUID: string |
33 | let videoUserUUID: string | 34 | let videoUserUUID: string |
34 | let verificationString: string | 35 | let verificationString: string |
36 | let verificationString2: string | ||
35 | const emails: object[] = [] | 37 | const emails: object[] = [] |
36 | const user = { | 38 | const user = { |
37 | username: 'user_1', | 39 | username: 'user_1', |
@@ -122,6 +124,56 @@ describe('Test emails', function () { | |||
122 | }) | 124 | }) |
123 | }) | 125 | }) |
124 | 126 | ||
127 | describe('When creating a user without password', function () { | ||
128 | it('Should send a create password email', async function () { | ||
129 | this.timeout(10000) | ||
130 | |||
131 | await createUser({ | ||
132 | url: server.url, | ||
133 | accessToken: server.accessToken, | ||
134 | username: 'create_password', | ||
135 | password: '' | ||
136 | }) | ||
137 | |||
138 | await waitJobs(server) | ||
139 | expect(emails).to.have.lengthOf(2) | ||
140 | |||
141 | const email = emails[1] | ||
142 | |||
143 | expect(email['from'][0]['name']).equal('localhost:' + server.port) | ||
144 | expect(email['from'][0]['address']).equal('test-admin@localhost') | ||
145 | expect(email['to'][0]['address']).equal('create_password@example.com') | ||
146 | expect(email['subject']).contains('account') | ||
147 | expect(email['subject']).contains('password') | ||
148 | |||
149 | const verificationStringMatches = /verificationString=([a-z0-9]+)/.exec(email['text']) | ||
150 | expect(verificationStringMatches).not.to.be.null | ||
151 | |||
152 | verificationString2 = verificationStringMatches[1] | ||
153 | expect(verificationString2).to.have.length.above(2) | ||
154 | |||
155 | const userIdMatches = /userId=([0-9]+)/.exec(email['text']) | ||
156 | expect(userIdMatches).not.to.be.null | ||
157 | |||
158 | userId2 = parseInt(userIdMatches[1], 10) | ||
159 | }) | ||
160 | |||
161 | it('Should not reset the password with an invalid verification string', async function () { | ||
162 | await resetPassword(server.url, userId2, verificationString2 + 'c', 'newly_created_password', 403) | ||
163 | }) | ||
164 | |||
165 | it('Should reset the password', async function () { | ||
166 | await resetPassword(server.url, userId2, verificationString2, 'newly_created_password') | ||
167 | }) | ||
168 | |||
169 | it('Should login with this new password', async function () { | ||
170 | await userLogin(server, { | ||
171 | username: 'create_password', | ||
172 | password: 'newly_created_password' | ||
173 | }) | ||
174 | }) | ||
175 | }) | ||
176 | |||
125 | describe('When creating a video abuse', function () { | 177 | describe('When creating a video abuse', function () { |
126 | it('Should send the notification email', async function () { | 178 | it('Should send the notification email', async function () { |
127 | this.timeout(10000) | 179 | this.timeout(10000) |
@@ -130,9 +182,9 @@ describe('Test emails', function () { | |||
130 | await reportVideoAbuse(server.url, server.accessToken, videoUUID, reason) | 182 | await reportVideoAbuse(server.url, server.accessToken, videoUUID, reason) |
131 | 183 | ||
132 | await waitJobs(server) | 184 | await waitJobs(server) |
133 | expect(emails).to.have.lengthOf(2) | 185 | expect(emails).to.have.lengthOf(3) |
134 | 186 | ||
135 | const email = emails[1] | 187 | const email = emails[2] |
136 | 188 | ||
137 | expect(email['from'][0]['name']).equal('localhost:' + server.port) | 189 | expect(email['from'][0]['name']).equal('localhost:' + server.port) |
138 | expect(email['from'][0]['address']).equal('test-admin@localhost') | 190 | expect(email['from'][0]['address']).equal('test-admin@localhost') |
@@ -151,9 +203,9 @@ describe('Test emails', function () { | |||
151 | await blockUser(server.url, userId, server.accessToken, 204, reason) | 203 | await blockUser(server.url, userId, server.accessToken, 204, reason) |
152 | 204 | ||
153 | await waitJobs(server) | 205 | await waitJobs(server) |
154 | expect(emails).to.have.lengthOf(3) | 206 | expect(emails).to.have.lengthOf(4) |
155 | 207 | ||
156 | const email = emails[2] | 208 | const email = emails[3] |
157 | 209 | ||
158 | expect(email['from'][0]['name']).equal('localhost:' + server.port) | 210 | expect(email['from'][0]['name']).equal('localhost:' + server.port) |
159 | expect(email['from'][0]['address']).equal('test-admin@localhost') | 211 | expect(email['from'][0]['address']).equal('test-admin@localhost') |
@@ -169,9 +221,9 @@ describe('Test emails', function () { | |||
169 | await unblockUser(server.url, userId, server.accessToken, 204) | 221 | await unblockUser(server.url, userId, server.accessToken, 204) |
170 | 222 | ||
171 | await waitJobs(server) | 223 | await waitJobs(server) |
172 | expect(emails).to.have.lengthOf(4) | 224 | expect(emails).to.have.lengthOf(5) |
173 | 225 | ||
174 | const email = emails[3] | 226 | const email = emails[4] |
175 | 227 | ||
176 | expect(email['from'][0]['name']).equal('localhost:' + server.port) | 228 | expect(email['from'][0]['name']).equal('localhost:' + server.port) |
177 | expect(email['from'][0]['address']).equal('test-admin@localhost') | 229 | expect(email['from'][0]['address']).equal('test-admin@localhost') |
@@ -189,9 +241,9 @@ describe('Test emails', function () { | |||
189 | await addVideoToBlacklist(server.url, server.accessToken, videoUserUUID, reason) | 241 | await addVideoToBlacklist(server.url, server.accessToken, videoUserUUID, reason) |
190 | 242 | ||
191 | await waitJobs(server) | 243 | await waitJobs(server) |
192 | expect(emails).to.have.lengthOf(5) | 244 | expect(emails).to.have.lengthOf(6) |
193 | 245 | ||
194 | const email = emails[4] | 246 | const email = emails[5] |
195 | 247 | ||
196 | expect(email['from'][0]['name']).equal('localhost:' + server.port) | 248 | expect(email['from'][0]['name']).equal('localhost:' + server.port) |
197 | expect(email['from'][0]['address']).equal('test-admin@localhost') | 249 | expect(email['from'][0]['address']).equal('test-admin@localhost') |
@@ -207,9 +259,9 @@ describe('Test emails', function () { | |||
207 | await removeVideoFromBlacklist(server.url, server.accessToken, videoUserUUID) | 259 | await removeVideoFromBlacklist(server.url, server.accessToken, videoUserUUID) |
208 | 260 | ||
209 | await waitJobs(server) | 261 | await waitJobs(server) |
210 | expect(emails).to.have.lengthOf(6) | 262 | expect(emails).to.have.lengthOf(7) |
211 | 263 | ||
212 | const email = emails[5] | 264 | const email = emails[6] |
213 | 265 | ||
214 | expect(email['from'][0]['name']).equal('localhost:' + server.port) | 266 | expect(email['from'][0]['name']).equal('localhost:' + server.port) |
215 | expect(email['from'][0]['address']).equal('test-admin@localhost') | 267 | expect(email['from'][0]['address']).equal('test-admin@localhost') |
@@ -227,9 +279,9 @@ describe('Test emails', function () { | |||
227 | await askSendVerifyEmail(server.url, 'user_1@example.com') | 279 | await askSendVerifyEmail(server.url, 'user_1@example.com') |
228 | 280 | ||
229 | await waitJobs(server) | 281 | await waitJobs(server) |
230 | expect(emails).to.have.lengthOf(7) | 282 | expect(emails).to.have.lengthOf(8) |
231 | 283 | ||
232 | const email = emails[6] | 284 | const email = emails[7] |
233 | 285 | ||
234 | expect(email['from'][0]['name']).equal('localhost:' + server.port) | 286 | expect(email['from'][0]['name']).equal('localhost:' + server.port) |
235 | expect(email['from'][0]['address']).equal('test-admin@localhost') | 287 | expect(email['from'][0]['address']).equal('test-admin@localhost') |
diff --git a/server/tests/api/server/follow-constraints.ts b/server/tests/api/server/follow-constraints.ts index 46663bf7c..a73440286 100644 --- a/server/tests/api/server/follow-constraints.ts +++ b/server/tests/api/server/follow-constraints.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -35,11 +35,11 @@ describe('Test follow constraints', function () { | |||
35 | await setAccessTokensToServers(servers) | 35 | await setAccessTokensToServers(servers) |
36 | 36 | ||
37 | { | 37 | { |
38 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video server 1' }) | 38 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video server 1' }) |
39 | video1UUID = res.body.video.uuid | 39 | video1UUID = res.body.video.uuid |
40 | } | 40 | } |
41 | { | 41 | { |
42 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video server 2' }) | 42 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video server 2' }) |
43 | video2UUID = res.body.video.uuid | 43 | video2UUID = res.body.video.uuid |
44 | } | 44 | } |
45 | 45 | ||
@@ -47,7 +47,7 @@ describe('Test follow constraints', function () { | |||
47 | username: 'user1', | 47 | username: 'user1', |
48 | password: 'super_password' | 48 | password: 'super_password' |
49 | } | 49 | } |
50 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) | 50 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) |
51 | userAccessToken = await userLogin(servers[0], user) | 51 | userAccessToken = await userLogin(servers[0], user) |
52 | 52 | ||
53 | await doubleFollow(servers[0], servers[1]) | 53 | await doubleFollow(servers[0], servers[1]) |
diff --git a/server/tests/api/server/follows-moderation.ts b/server/tests/api/server/follows-moderation.ts index 1984c9eb1..cee85cc4b 100644 --- a/server/tests/api/server/follows-moderation.ts +++ b/server/tests/api/server/follows-moderation.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -24,7 +24,7 @@ const expect = chai.expect | |||
24 | 24 | ||
25 | async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') { | 25 | async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') { |
26 | { | 26 | { |
27 | const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) | 27 | const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' }) |
28 | expect(res.body.total).to.equal(1) | 28 | expect(res.body.total).to.equal(1) |
29 | 29 | ||
30 | const follow = res.body.data[0] as ActorFollow | 30 | const follow = res.body.data[0] as ActorFollow |
@@ -34,7 +34,7 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc | |||
34 | } | 34 | } |
35 | 35 | ||
36 | { | 36 | { |
37 | const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) | 37 | const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' }) |
38 | expect(res.body.total).to.equal(1) | 38 | expect(res.body.total).to.equal(1) |
39 | 39 | ||
40 | const follow = res.body.data[0] as ActorFollow | 40 | const follow = res.body.data[0] as ActorFollow |
@@ -46,12 +46,12 @@ async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'acc | |||
46 | 46 | ||
47 | async function checkNoFollowers (servers: ServerInfo[]) { | 47 | async function checkNoFollowers (servers: ServerInfo[]) { |
48 | { | 48 | { |
49 | const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) | 49 | const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' }) |
50 | expect(res.body.total).to.equal(0) | 50 | expect(res.body.total).to.equal(0) |
51 | } | 51 | } |
52 | 52 | ||
53 | { | 53 | { |
54 | const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) | 54 | const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' }) |
55 | expect(res.body.total).to.equal(0) | 55 | expect(res.body.total).to.equal(0) |
56 | } | 56 | } |
57 | } | 57 | } |
@@ -164,17 +164,17 @@ describe('Test follows moderation', function () { | |||
164 | await waitJobs(servers) | 164 | await waitJobs(servers) |
165 | 165 | ||
166 | { | 166 | { |
167 | const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) | 167 | const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' }) |
168 | expect(res.body.total).to.equal(2) | 168 | expect(res.body.total).to.equal(2) |
169 | } | 169 | } |
170 | 170 | ||
171 | { | 171 | { |
172 | const res = await getFollowersListPaginationAndSort({ url: servers[ 1 ].url, start: 0, count: 5, sort: 'createdAt' }) | 172 | const res = await getFollowersListPaginationAndSort({ url: servers[1].url, start: 0, count: 5, sort: 'createdAt' }) |
173 | expect(res.body.total).to.equal(1) | 173 | expect(res.body.total).to.equal(1) |
174 | } | 174 | } |
175 | 175 | ||
176 | { | 176 | { |
177 | const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 5, sort: 'createdAt' }) | 177 | const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 5, sort: 'createdAt' }) |
178 | expect(res.body.total).to.equal(1) | 178 | expect(res.body.total).to.equal(1) |
179 | } | 179 | } |
180 | 180 | ||
@@ -184,7 +184,7 @@ describe('Test follows moderation', function () { | |||
184 | await checkServer1And2HasFollowers(servers) | 184 | await checkServer1And2HasFollowers(servers) |
185 | 185 | ||
186 | { | 186 | { |
187 | const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 5, sort: 'createdAt' }) | 187 | const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 5, sort: 'createdAt' }) |
188 | expect(res.body.total).to.equal(0) | 188 | expect(res.body.total).to.equal(0) |
189 | } | 189 | } |
190 | }) | 190 | }) |
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index 4ffa9e791..b686af4e4 100644 --- a/server/tests/api/server/follows.ts +++ b/server/tests/api/server/follows.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -78,14 +78,14 @@ describe('Test follows', function () { | |||
78 | }) | 78 | }) |
79 | 79 | ||
80 | it('Should have 2 followings on server 1', async function () { | 80 | it('Should have 2 followings on server 1', async function () { |
81 | let res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 1, sort: 'createdAt' }) | 81 | let res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 1, sort: 'createdAt' }) |
82 | let follows = res.body.data | 82 | let follows = res.body.data |
83 | 83 | ||
84 | expect(res.body.total).to.equal(2) | 84 | expect(res.body.total).to.equal(2) |
85 | expect(follows).to.be.an('array') | 85 | expect(follows).to.be.an('array') |
86 | expect(follows.length).to.equal(1) | 86 | expect(follows.length).to.equal(1) |
87 | 87 | ||
88 | res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 1, count: 1, sort: 'createdAt' }) | 88 | res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 1, count: 1, sort: 'createdAt' }) |
89 | follows = follows.concat(res.body.data) | 89 | follows = follows.concat(res.body.data) |
90 | 90 | ||
91 | const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port) | 91 | const server2Follow = follows.find(f => f.following.host === 'localhost:' + servers[1].port) |
@@ -101,7 +101,7 @@ describe('Test follows', function () { | |||
101 | const sort = 'createdAt' | 101 | const sort = 'createdAt' |
102 | const start = 0 | 102 | const start = 0 |
103 | const count = 1 | 103 | const count = 1 |
104 | const url = servers[ 0 ].url | 104 | const url = servers[0].url |
105 | 105 | ||
106 | { | 106 | { |
107 | const search = ':' + servers[1].port | 107 | const search = ':' + servers[1].port |
@@ -112,7 +112,7 @@ describe('Test follows', function () { | |||
112 | 112 | ||
113 | expect(res.body.total).to.equal(1) | 113 | expect(res.body.total).to.equal(1) |
114 | expect(follows.length).to.equal(1) | 114 | expect(follows.length).to.equal(1) |
115 | expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[ 1 ].port) | 115 | expect(follows[0].following.host).to.equal('localhost:' + servers[1].port) |
116 | } | 116 | } |
117 | 117 | ||
118 | { | 118 | { |
@@ -170,9 +170,9 @@ describe('Test follows', function () { | |||
170 | 170 | ||
171 | it('Should have 1 followers on server 2 and 3', async function () { | 171 | it('Should have 1 followers on server 2 and 3', async function () { |
172 | for (const server of [ servers[1], servers[2] ]) { | 172 | for (const server of [ servers[1], servers[2] ]) { |
173 | let res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 1, sort: 'createdAt' }) | 173 | const res = await getFollowersListPaginationAndSort({ url: server.url, start: 0, count: 1, sort: 'createdAt' }) |
174 | 174 | ||
175 | let follows = res.body.data | 175 | const follows = res.body.data |
176 | expect(res.body.total).to.equal(1) | 176 | expect(res.body.total).to.equal(1) |
177 | expect(follows).to.be.an('array') | 177 | expect(follows).to.be.an('array') |
178 | expect(follows.length).to.equal(1) | 178 | expect(follows.length).to.equal(1) |
@@ -181,7 +181,7 @@ describe('Test follows', function () { | |||
181 | }) | 181 | }) |
182 | 182 | ||
183 | it('Should search/filter followers on server 2', async function () { | 183 | it('Should search/filter followers on server 2', async function () { |
184 | const url = servers[ 2 ].url | 184 | const url = servers[2].url |
185 | const start = 0 | 185 | const start = 0 |
186 | const count = 5 | 186 | const count = 5 |
187 | const sort = 'createdAt' | 187 | const sort = 'createdAt' |
@@ -195,7 +195,7 @@ describe('Test follows', function () { | |||
195 | 195 | ||
196 | expect(res.body.total).to.equal(1) | 196 | expect(res.body.total).to.equal(1) |
197 | expect(follows.length).to.equal(1) | 197 | expect(follows.length).to.equal(1) |
198 | expect(follows[ 0 ].following.host).to.equal('localhost:' + servers[ 2 ].port) | 198 | expect(follows[0].following.host).to.equal('localhost:' + servers[2].port) |
199 | } | 199 | } |
200 | 200 | ||
201 | { | 201 | { |
@@ -241,7 +241,7 @@ describe('Test follows', function () { | |||
241 | }) | 241 | }) |
242 | 242 | ||
243 | it('Should have 0 followers on server 1', async function () { | 243 | it('Should have 0 followers on server 1', async function () { |
244 | const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 5, sort: 'createdAt' }) | 244 | const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 5, sort: 'createdAt' }) |
245 | const follows = res.body.data | 245 | const follows = res.body.data |
246 | 246 | ||
247 | expect(res.body.total).to.equal(0) | 247 | expect(res.body.total).to.equal(0) |
@@ -271,8 +271,8 @@ describe('Test follows', function () { | |||
271 | }) | 271 | }) |
272 | 272 | ||
273 | it('Should not follow server 3 on server 1 anymore', async function () { | 273 | it('Should not follow server 3 on server 1 anymore', async function () { |
274 | const res = await getFollowingListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) | 274 | const res = await getFollowingListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' }) |
275 | let follows = res.body.data | 275 | const follows = res.body.data |
276 | 276 | ||
277 | expect(res.body.total).to.equal(1) | 277 | expect(res.body.total).to.equal(1) |
278 | expect(follows).to.be.an('array') | 278 | expect(follows).to.be.an('array') |
@@ -282,9 +282,9 @@ describe('Test follows', function () { | |||
282 | }) | 282 | }) |
283 | 283 | ||
284 | it('Should not have server 1 as follower on server 3 anymore', async function () { | 284 | it('Should not have server 1 as follower on server 3 anymore', async function () { |
285 | const res = await getFollowersListPaginationAndSort({ url: servers[ 2 ].url, start: 0, count: 1, sort: 'createdAt' }) | 285 | const res = await getFollowersListPaginationAndSort({ url: servers[2].url, start: 0, count: 1, sort: 'createdAt' }) |
286 | 286 | ||
287 | let follows = res.body.data | 287 | const follows = res.body.data |
288 | expect(res.body.total).to.equal(0) | 288 | expect(res.body.total).to.equal(0) |
289 | expect(follows).to.be.an('array') | 289 | expect(follows).to.be.an('array') |
290 | expect(follows.length).to.equal(0) | 290 | expect(follows.length).to.equal(0) |
@@ -336,59 +336,59 @@ describe('Test follows', function () { | |||
336 | tags: [ 'tag1', 'tag2', 'tag3' ] | 336 | tags: [ 'tag1', 'tag2', 'tag3' ] |
337 | } | 337 | } |
338 | 338 | ||
339 | await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-2' }) | 339 | await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-2' }) |
340 | await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-3' }) | 340 | await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-3' }) |
341 | await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, video4Attributes) | 341 | await uploadVideo(servers[2].url, servers[2].accessToken, video4Attributes) |
342 | await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-5' }) | 342 | await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-5' }) |
343 | await uploadVideo(servers[ 2 ].url, servers[ 2 ].accessToken, { name: 'server3-6' }) | 343 | await uploadVideo(servers[2].url, servers[2].accessToken, { name: 'server3-6' }) |
344 | 344 | ||
345 | { | 345 | { |
346 | const user = { username: 'captain', password: 'password' } | 346 | const user = { username: 'captain', password: 'password' } |
347 | await createUser({ url: servers[ 2 ].url, accessToken: servers[ 2 ].accessToken, username: user.username, password: user.password }) | 347 | await createUser({ url: servers[2].url, accessToken: servers[2].accessToken, username: user.username, password: user.password }) |
348 | const userAccessToken = await userLogin(servers[ 2 ], user) | 348 | const userAccessToken = await userLogin(servers[2], user) |
349 | 349 | ||
350 | const resVideos = await getVideosList(servers[ 2 ].url) | 350 | const resVideos = await getVideosList(servers[2].url) |
351 | video4 = resVideos.body.data.find(v => v.name === 'server3-4') | 351 | video4 = resVideos.body.data.find(v => v.name === 'server3-4') |
352 | 352 | ||
353 | { | 353 | { |
354 | await rateVideo(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, 'like') | 354 | await rateVideo(servers[2].url, servers[2].accessToken, video4.id, 'like') |
355 | await rateVideo(servers[ 2 ].url, userAccessToken, video4.id, 'dislike') | 355 | await rateVideo(servers[2].url, userAccessToken, video4.id, 'dislike') |
356 | } | 356 | } |
357 | 357 | ||
358 | { | 358 | { |
359 | { | 359 | { |
360 | const text = 'my super first comment' | 360 | const text = 'my super first comment' |
361 | const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) | 361 | const res = await addVideoCommentThread(servers[2].url, servers[2].accessToken, video4.id, text) |
362 | const threadId = res.body.comment.id | 362 | const threadId = res.body.comment.id |
363 | 363 | ||
364 | const text1 = 'my super answer to thread 1' | 364 | const text1 = 'my super answer to thread 1' |
365 | const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) | 365 | const childCommentRes = await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text1) |
366 | const childCommentId = childCommentRes.body.comment.id | 366 | const childCommentId = childCommentRes.body.comment.id |
367 | 367 | ||
368 | const text2 = 'my super answer to answer of thread 1' | 368 | const text2 = 'my super answer to answer of thread 1' |
369 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text2) | 369 | await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, childCommentId, text2) |
370 | 370 | ||
371 | const text3 = 'my second answer to thread 1' | 371 | const text3 = 'my second answer to thread 1' |
372 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text3) | 372 | await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text3) |
373 | } | 373 | } |
374 | 374 | ||
375 | { | 375 | { |
376 | const text = 'will be deleted' | 376 | const text = 'will be deleted' |
377 | const res = await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, text) | 377 | const res = await addVideoCommentThread(servers[2].url, servers[2].accessToken, video4.id, text) |
378 | const threadId = res.body.comment.id | 378 | const threadId = res.body.comment.id |
379 | 379 | ||
380 | const text1 = 'answer to deleted' | 380 | const text1 = 'answer to deleted' |
381 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text1) | 381 | await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text1) |
382 | 382 | ||
383 | const text2 = 'will also be deleted' | 383 | const text2 = 'will also be deleted' |
384 | const childCommentRes = await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId, text2) | 384 | const childCommentRes = await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, threadId, text2) |
385 | const childCommentId = childCommentRes.body.comment.id | 385 | const childCommentId = childCommentRes.body.comment.id |
386 | 386 | ||
387 | const text3 = 'my second answer to deleted' | 387 | const text3 = 'my second answer to deleted' |
388 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId, text3) | 388 | await addVideoCommentReply(servers[2].url, servers[2].accessToken, video4.id, childCommentId, text3) |
389 | 389 | ||
390 | await deleteVideoComment(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, threadId) | 390 | await deleteVideoComment(servers[2].url, servers[2].accessToken, video4.id, threadId) |
391 | await deleteVideoComment(servers[ 2 ].url, servers[ 2 ].accessToken, video4.id, childCommentId) | 391 | await deleteVideoComment(servers[2].url, servers[2].accessToken, video4.id, childCommentId) |
392 | } | 392 | } |
393 | } | 393 | } |
394 | 394 | ||
@@ -406,7 +406,7 @@ describe('Test follows', function () { | |||
406 | await waitJobs(servers) | 406 | await waitJobs(servers) |
407 | 407 | ||
408 | // Server 1 follows server 3 | 408 | // Server 1 follows server 3 |
409 | await follow(servers[ 0 ].url, [ servers[ 2 ].url ], servers[ 0 ].accessToken) | 409 | await follow(servers[0].url, [ servers[2].url ], servers[0].accessToken) |
410 | 410 | ||
411 | await waitJobs(servers) | 411 | await waitJobs(servers) |
412 | }) | 412 | }) |
@@ -424,7 +424,7 @@ describe('Test follows', function () { | |||
424 | }) | 424 | }) |
425 | 425 | ||
426 | it('Should have propagated videos', async function () { | 426 | it('Should have propagated videos', async function () { |
427 | const res = await getVideosList(servers[ 0 ].url) | 427 | const res = await getVideosList(servers[0].url) |
428 | expect(res.body.total).to.equal(7) | 428 | expect(res.body.total).to.equal(7) |
429 | 429 | ||
430 | const video2 = res.body.data.find(v => v.name === 'server3-2') | 430 | const video2 = res.body.data.find(v => v.name === 'server3-2') |
@@ -470,7 +470,7 @@ describe('Test follows', function () { | |||
470 | } | 470 | } |
471 | ] | 471 | ] |
472 | } | 472 | } |
473 | await completeVideoCheck(servers[ 0 ].url, video4, checkAttributes) | 473 | await completeVideoCheck(servers[0].url, video4, checkAttributes) |
474 | }) | 474 | }) |
475 | 475 | ||
476 | it('Should have propagated comments', async function () { | 476 | it('Should have propagated comments', async function () { |
@@ -481,34 +481,34 @@ describe('Test follows', function () { | |||
481 | expect(res1.body.data).to.have.lengthOf(2) | 481 | expect(res1.body.data).to.have.lengthOf(2) |
482 | 482 | ||
483 | { | 483 | { |
484 | const comment: VideoComment = res1.body.data[ 0 ] | 484 | const comment: VideoComment = res1.body.data[0] |
485 | expect(comment.inReplyToCommentId).to.be.null | 485 | expect(comment.inReplyToCommentId).to.be.null |
486 | expect(comment.text).equal('my super first comment') | 486 | expect(comment.text).equal('my super first comment') |
487 | expect(comment.videoId).to.equal(video4.id) | 487 | expect(comment.videoId).to.equal(video4.id) |
488 | expect(comment.id).to.equal(comment.threadId) | 488 | expect(comment.id).to.equal(comment.threadId) |
489 | expect(comment.account.name).to.equal('root') | 489 | expect(comment.account.name).to.equal('root') |
490 | expect(comment.account.host).to.equal('localhost:' + servers[ 2 ].port) | 490 | expect(comment.account.host).to.equal('localhost:' + servers[2].port) |
491 | expect(comment.totalReplies).to.equal(3) | 491 | expect(comment.totalReplies).to.equal(3) |
492 | expect(dateIsValid(comment.createdAt as string)).to.be.true | 492 | expect(dateIsValid(comment.createdAt as string)).to.be.true |
493 | expect(dateIsValid(comment.updatedAt as string)).to.be.true | 493 | expect(dateIsValid(comment.updatedAt as string)).to.be.true |
494 | 494 | ||
495 | const threadId = comment.threadId | 495 | const threadId = comment.threadId |
496 | 496 | ||
497 | const res2 = await getVideoThreadComments(servers[ 0 ].url, video4.id, threadId) | 497 | const res2 = await getVideoThreadComments(servers[0].url, video4.id, threadId) |
498 | 498 | ||
499 | const tree: VideoCommentThreadTree = res2.body | 499 | const tree: VideoCommentThreadTree = res2.body |
500 | expect(tree.comment.text).equal('my super first comment') | 500 | expect(tree.comment.text).equal('my super first comment') |
501 | expect(tree.children).to.have.lengthOf(2) | 501 | expect(tree.children).to.have.lengthOf(2) |
502 | 502 | ||
503 | const firstChild = tree.children[ 0 ] | 503 | const firstChild = tree.children[0] |
504 | expect(firstChild.comment.text).to.equal('my super answer to thread 1') | 504 | expect(firstChild.comment.text).to.equal('my super answer to thread 1') |
505 | expect(firstChild.children).to.have.lengthOf(1) | 505 | expect(firstChild.children).to.have.lengthOf(1) |
506 | 506 | ||
507 | const childOfFirstChild = firstChild.children[ 0 ] | 507 | const childOfFirstChild = firstChild.children[0] |
508 | expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') | 508 | expect(childOfFirstChild.comment.text).to.equal('my super answer to answer of thread 1') |
509 | expect(childOfFirstChild.children).to.have.lengthOf(0) | 509 | expect(childOfFirstChild.children).to.have.lengthOf(0) |
510 | 510 | ||
511 | const secondChild = tree.children[ 1 ] | 511 | const secondChild = tree.children[1] |
512 | expect(secondChild.comment.text).to.equal('my second answer to thread 1') | 512 | expect(secondChild.comment.text).to.equal('my second answer to thread 1') |
513 | expect(secondChild.children).to.have.lengthOf(0) | 513 | expect(secondChild.children).to.have.lengthOf(0) |
514 | } | 514 | } |
@@ -569,7 +569,7 @@ describe('Test follows', function () { | |||
569 | 569 | ||
570 | await waitJobs(servers) | 570 | await waitJobs(servers) |
571 | 571 | ||
572 | let res = await getVideosList(servers[ 0 ].url) | 572 | const res = await getVideosList(servers[0].url) |
573 | expect(res.body.total).to.equal(1) | 573 | expect(res.body.total).to.equal(1) |
574 | }) | 574 | }) |
575 | 575 | ||
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts index 7e36067f1..2cf6e15ad 100644 --- a/server/tests/api/server/handle-down.ts +++ b/server/tests/api/server/handle-down.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -8,6 +8,7 @@ import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-c | |||
8 | 8 | ||
9 | import { | 9 | import { |
10 | cleanupTests, | 10 | cleanupTests, |
11 | closeAllSequelize, | ||
11 | completeVideoCheck, | 12 | completeVideoCheck, |
12 | flushAndRunMultipleServers, | 13 | flushAndRunMultipleServers, |
13 | getVideo, | 14 | getVideo, |
@@ -17,11 +18,12 @@ import { | |||
17 | reRunServer, | 18 | reRunServer, |
18 | ServerInfo, | 19 | ServerInfo, |
19 | setAccessTokensToServers, | 20 | setAccessTokensToServers, |
21 | setActorFollowScores, | ||
20 | unfollow, | 22 | unfollow, |
21 | updateVideo, | 23 | updateVideo, |
22 | uploadVideo, uploadVideoAndGetId, | 24 | uploadVideo, |
23 | wait, | 25 | uploadVideoAndGetId, |
24 | setActorFollowScores, closeAllSequelize | 26 | wait |
25 | } from '../../../../shared/extra-utils' | 27 | } from '../../../../shared/extra-utils' |
26 | import { follow, getFollowersListPaginationAndSort } from '../../../../shared/extra-utils/server/follows' | 28 | import { follow, getFollowersListPaginationAndSort } from '../../../../shared/extra-utils/server/follows' |
27 | import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' | 29 | import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' |
@@ -44,7 +46,7 @@ describe('Test handle downs', function () { | |||
44 | let missedVideo2: Video | 46 | let missedVideo2: Video |
45 | let unlistedVideo: Video | 47 | let unlistedVideo: Video |
46 | 48 | ||
47 | let videoIdsServer1: number[] = [] | 49 | const videoIdsServer1: number[] = [] |
48 | 50 | ||
49 | const videoAttributes = { | 51 | const videoAttributes = { |
50 | name: 'my super name for server 1', | 52 | name: 'my super name for server 1', |
@@ -137,7 +139,7 @@ describe('Test handle downs', function () { | |||
137 | 139 | ||
138 | // Remove server 2 follower | 140 | // Remove server 2 follower |
139 | for (let i = 0; i < 10; i++) { | 141 | for (let i = 0; i < 10; i++) { |
140 | await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) | 142 | await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) |
141 | } | 143 | } |
142 | 144 | ||
143 | await waitJobs(servers[0]) | 145 | await waitJobs(servers[0]) |
@@ -145,14 +147,14 @@ describe('Test handle downs', function () { | |||
145 | // Kill server 3 | 147 | // Kill server 3 |
146 | killallServers([ servers[2] ]) | 148 | killallServers([ servers[2] ]) |
147 | 149 | ||
148 | const resLastVideo1 = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) | 150 | const resLastVideo1 = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) |
149 | missedVideo1 = resLastVideo1.body.video | 151 | missedVideo1 = resLastVideo1.body.video |
150 | 152 | ||
151 | const resLastVideo2 = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) | 153 | const resLastVideo2 = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) |
152 | missedVideo2 = resLastVideo2.body.video | 154 | missedVideo2 = resLastVideo2.body.video |
153 | 155 | ||
154 | // Unlisted video | 156 | // Unlisted video |
155 | let resVideo = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, unlistedVideoAttributes) | 157 | const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, unlistedVideoAttributes) |
156 | unlistedVideo = resVideo.body.video | 158 | unlistedVideo = resVideo.body.video |
157 | 159 | ||
158 | // Add comments to video 2 | 160 | // Add comments to video 2 |
@@ -174,7 +176,7 @@ describe('Test handle downs', function () { | |||
174 | await wait(11000) | 176 | await wait(11000) |
175 | 177 | ||
176 | // Only server 3 is still a follower of server 1 | 178 | // Only server 3 is still a follower of server 1 |
177 | const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) | 179 | const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' }) |
178 | expect(res.body.data).to.be.an('array') | 180 | expect(res.body.data).to.be.an('array') |
179 | expect(res.body.data).to.have.lengthOf(1) | 181 | expect(res.body.data).to.have.lengthOf(1) |
180 | expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port) | 182 | expect(res.body.data[0].follower.host).to.equal('localhost:' + servers[2].port) |
@@ -185,8 +187,8 @@ describe('Test handle downs', function () { | |||
185 | 187 | ||
186 | for (const state of states) { | 188 | for (const state of states) { |
187 | const res = await getJobsListPaginationAndSort({ | 189 | const res = await getJobsListPaginationAndSort({ |
188 | url: servers[ 0 ].url, | 190 | url: servers[0].url, |
189 | accessToken: servers[ 0 ].accessToken, | 191 | accessToken: servers[0].accessToken, |
190 | state: state, | 192 | state: state, |
191 | start: 0, | 193 | start: 0, |
192 | count: 50, | 194 | count: 50, |
@@ -209,7 +211,7 @@ describe('Test handle downs', function () { | |||
209 | 211 | ||
210 | await waitJobs(servers) | 212 | await waitJobs(servers) |
211 | 213 | ||
212 | const res = await getFollowersListPaginationAndSort({ url: servers[ 0 ].url, start: 0, count: 2, sort: 'createdAt' }) | 214 | const res = await getFollowersListPaginationAndSort({ url: servers[0].url, start: 0, count: 2, sort: 'createdAt' }) |
213 | expect(res.body.data).to.be.an('array') | 215 | expect(res.body.data).to.be.an('array') |
214 | expect(res.body.data).to.have.lengthOf(2) | 216 | expect(res.body.data).to.have.lengthOf(2) |
215 | }) | 217 | }) |
@@ -221,8 +223,8 @@ describe('Test handle downs', function () { | |||
221 | expect(res1.body.data).to.be.an('array') | 223 | expect(res1.body.data).to.be.an('array') |
222 | expect(res1.body.data).to.have.lengthOf(11) | 224 | expect(res1.body.data).to.have.lengthOf(11) |
223 | 225 | ||
224 | await updateVideo(servers[0].url, servers[0].accessToken, missedVideo1.uuid, { }) | 226 | await updateVideo(servers[0].url, servers[0].accessToken, missedVideo1.uuid, {}) |
225 | await updateVideo(servers[0].url, servers[0].accessToken, unlistedVideo.uuid, { }) | 227 | await updateVideo(servers[0].url, servers[0].accessToken, unlistedVideo.uuid, {}) |
226 | 228 | ||
227 | await waitJobs(servers) | 229 | await waitJobs(servers) |
228 | 230 | ||
@@ -313,14 +315,14 @@ describe('Test handle downs', function () { | |||
313 | this.timeout(120000) | 315 | this.timeout(120000) |
314 | 316 | ||
315 | for (let i = 0; i < 10; i++) { | 317 | for (let i = 0; i < 10; i++) { |
316 | const uuid = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'video ' + i })).uuid | 318 | const uuid = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video ' + i })).uuid |
317 | videoIdsServer1.push(uuid) | 319 | videoIdsServer1.push(uuid) |
318 | } | 320 | } |
319 | 321 | ||
320 | await waitJobs(servers) | 322 | await waitJobs(servers) |
321 | 323 | ||
322 | for (const id of videoIdsServer1) { | 324 | for (const id of videoIdsServer1) { |
323 | await getVideo(servers[ 1 ].url, id) | 325 | await getVideo(servers[1].url, id) |
324 | } | 326 | } |
325 | 327 | ||
326 | await waitJobs(servers) | 328 | await waitJobs(servers) |
diff --git a/server/tests/api/server/jobs.ts b/server/tests/api/server/jobs.ts index 58d8c8c10..19c8836b5 100644 --- a/server/tests/api/server/jobs.ts +++ b/server/tests/api/server/jobs.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { cleanupTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' | 5 | import { cleanupTests, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' |
6 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' | 6 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
7 | import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' | 7 | import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' |
8 | import { flushAndRunMultipleServers } from '../../../../shared/extra-utils/server/servers' | 8 | import { flushAndRunMultipleServers } from '../../../../shared/extra-utils/server/servers' |
@@ -44,8 +44,8 @@ describe('Test jobs', function () { | |||
44 | it('Should list jobs with sort, pagination and job type', async function () { | 44 | it('Should list jobs with sort, pagination and job type', async function () { |
45 | { | 45 | { |
46 | const res = await getJobsListPaginationAndSort({ | 46 | const res = await getJobsListPaginationAndSort({ |
47 | url: servers[ 1 ].url, | 47 | url: servers[1].url, |
48 | accessToken: servers[ 1 ].accessToken, | 48 | accessToken: servers[1].accessToken, |
49 | state: 'completed', | 49 | state: 'completed', |
50 | start: 1, | 50 | start: 1, |
51 | count: 2, | 51 | count: 2, |
@@ -54,9 +54,9 @@ describe('Test jobs', function () { | |||
54 | expect(res.body.total).to.be.above(2) | 54 | expect(res.body.total).to.be.above(2) |
55 | expect(res.body.data).to.have.lengthOf(2) | 55 | expect(res.body.data).to.have.lengthOf(2) |
56 | 56 | ||
57 | let job: Job = res.body.data[ 0 ] | 57 | let job: Job = res.body.data[0] |
58 | // Skip repeat jobs | 58 | // Skip repeat jobs |
59 | if (job.type === 'videos-views') job = res.body.data[ 1 ] | 59 | if (job.type === 'videos-views') job = res.body.data[1] |
60 | 60 | ||
61 | expect(job.state).to.equal('completed') | 61 | expect(job.state).to.equal('completed') |
62 | expect(job.type.startsWith('activitypub-')).to.be.true | 62 | expect(job.type.startsWith('activitypub-')).to.be.true |
@@ -67,8 +67,8 @@ describe('Test jobs', function () { | |||
67 | 67 | ||
68 | { | 68 | { |
69 | const res = await getJobsListPaginationAndSort({ | 69 | const res = await getJobsListPaginationAndSort({ |
70 | url: servers[ 1 ].url, | 70 | url: servers[1].url, |
71 | accessToken: servers[ 1 ].accessToken, | 71 | accessToken: servers[1].accessToken, |
72 | state: 'completed', | 72 | state: 'completed', |
73 | start: 0, | 73 | start: 0, |
74 | count: 100, | 74 | count: 100, |
diff --git a/server/tests/api/server/logs.ts b/server/tests/api/server/logs.ts index d3c877408..b8714c7a1 100644 --- a/server/tests/api/server/logs.ts +++ b/server/tests/api/server/logs.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
diff --git a/server/tests/api/server/no-client.ts b/server/tests/api/server/no-client.ts index 86edeb289..d0450aba0 100644 --- a/server/tests/api/server/no-client.ts +++ b/server/tests/api/server/no-client.ts | |||
@@ -9,7 +9,7 @@ describe('Start and stop server without web client routes', function () { | |||
9 | before(async function () { | 9 | before(async function () { |
10 | this.timeout(30000) | 10 | this.timeout(30000) |
11 | 11 | ||
12 | server = await flushAndRunServer(1, {}, ['--no-client']) | 12 | server = await flushAndRunServer(1, {}, [ '--no-client' ]) |
13 | }) | 13 | }) |
14 | 14 | ||
15 | it('Should fail getting the client', function () { | 15 | it('Should fail getting the client', function () { |
diff --git a/server/tests/api/server/plugins.ts b/server/tests/api/server/plugins.ts index b8a8a2fee..9885be4e8 100644 --- a/server/tests/api/server/plugins.ts +++ b/server/tests/api/server/plugins.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
@@ -6,19 +6,29 @@ import { | |||
6 | cleanupTests, | 6 | cleanupTests, |
7 | closeAllSequelize, | 7 | closeAllSequelize, |
8 | flushAndRunServer, | 8 | flushAndRunServer, |
9 | getConfig, getMyUserInformation, getPluginPackageJSON, | 9 | getConfig, |
10 | getMyUserInformation, | ||
10 | getPlugin, | 11 | getPlugin, |
12 | getPluginPackageJSON, | ||
11 | getPluginRegisteredSettings, | 13 | getPluginRegisteredSettings, |
12 | getPluginsCSS, | 14 | getPluginsCSS, |
13 | installPlugin, killallServers, | 15 | getPublicSettings, |
16 | installPlugin, | ||
17 | killallServers, | ||
14 | listAvailablePlugins, | 18 | listAvailablePlugins, |
15 | listPlugins, reRunServer, | 19 | listPlugins, |
20 | reRunServer, | ||
16 | ServerInfo, | 21 | ServerInfo, |
17 | setAccessTokensToServers, | 22 | setAccessTokensToServers, |
18 | setPluginVersion, uninstallPlugin, | 23 | setPluginVersion, |
19 | updateCustomSubConfig, updateMyUser, updatePluginPackageJSON, updatePlugin, | 24 | uninstallPlugin, |
25 | updateCustomSubConfig, | ||
26 | updateMyUser, | ||
27 | updatePlugin, | ||
28 | updatePluginPackageJSON, | ||
20 | updatePluginSettings, | 29 | updatePluginSettings, |
21 | wait, getPublicSettings | 30 | wait, |
31 | waitUntilLog | ||
22 | } from '../../../../shared/extra-utils' | 32 | } from '../../../../shared/extra-utils' |
23 | import { PluginType } from '../../../../shared/models/plugins/plugin.type' | 33 | import { PluginType } from '../../../../shared/models/plugins/plugin.type' |
24 | import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model' | 34 | import { PeerTubePluginIndex } from '../../../../shared/models/plugins/peertube-plugin-index.model' |
@@ -88,7 +98,7 @@ describe('Test plugins', function () { | |||
88 | expect(res2.body.total).to.be.at.least(2) | 98 | expect(res2.body.total).to.be.at.least(2) |
89 | expect(data2).to.have.lengthOf(2) | 99 | expect(data2).to.have.lengthOf(2) |
90 | 100 | ||
91 | expect(data1[0].npmName).to.not.equal(data2[ 0 ].npmName) | 101 | expect(data1[0].npmName).to.not.equal(data2[0].npmName) |
92 | } | 102 | } |
93 | 103 | ||
94 | { | 104 | { |
@@ -133,7 +143,7 @@ describe('Test plugins', function () { | |||
133 | it('Should have the correct global css', async function () { | 143 | it('Should have the correct global css', async function () { |
134 | const res = await getPluginsCSS(server.url) | 144 | const res = await getPluginsCSS(server.url) |
135 | 145 | ||
136 | expect(res.text).to.contain('--mainBackgroundColor') | 146 | expect(res.text).to.contain('background-color: red') |
137 | }) | 147 | }) |
138 | 148 | ||
139 | it('Should have the plugin loaded in the configuration', async function () { | 149 | it('Should have the plugin loaded in the configuration', async function () { |
@@ -249,6 +259,12 @@ describe('Test plugins', function () { | |||
249 | }) | 259 | }) |
250 | }) | 260 | }) |
251 | 261 | ||
262 | it('Should have watched settings changes', async function () { | ||
263 | this.timeout(10000) | ||
264 | |||
265 | await waitUntilLog(server, 'Settings changed!') | ||
266 | }) | ||
267 | |||
252 | it('Should get a plugin and a theme', async function () { | 268 | it('Should get a plugin and a theme', async function () { |
253 | { | 269 | { |
254 | const res = await getPlugin({ | 270 | const res = await getPlugin({ |
diff --git a/server/tests/api/server/reverse-proxy.ts b/server/tests/api/server/reverse-proxy.ts index b6b33a884..d0d79c4f6 100644 --- a/server/tests/api/server/reverse-proxy.ts +++ b/server/tests/api/server/reverse-proxy.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts index a01cd4b38..637525ff8 100644 --- a/server/tests/api/server/stats.ts +++ b/server/tests/api/server/stats.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -9,13 +9,13 @@ import { | |||
9 | doubleFollow, | 9 | doubleFollow, |
10 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
11 | follow, | 11 | follow, |
12 | killallServers, | 12 | ServerInfo, unfollow, |
13 | ServerInfo, | ||
14 | uploadVideo, | 13 | uploadVideo, |
15 | viewVideo, | 14 | viewVideo, |
16 | wait | 15 | wait, |
16 | userLogin | ||
17 | } from '../../../../shared/extra-utils' | 17 | } from '../../../../shared/extra-utils' |
18 | import { flushTests, setAccessTokensToServers } from '../../../../shared/extra-utils/index' | 18 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/index' |
19 | import { getStats } from '../../../../shared/extra-utils/server/stats' | 19 | import { getStats } from '../../../../shared/extra-utils/server/stats' |
20 | import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' | 20 | import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' |
21 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 21 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
@@ -24,6 +24,10 @@ const expect = chai.expect | |||
24 | 24 | ||
25 | describe('Test stats (excluding redundancy)', function () { | 25 | describe('Test stats (excluding redundancy)', function () { |
26 | let servers: ServerInfo[] = [] | 26 | let servers: ServerInfo[] = [] |
27 | const user = { | ||
28 | username: 'user1', | ||
29 | password: 'super_password' | ||
30 | } | ||
27 | 31 | ||
28 | before(async function () { | 32 | before(async function () { |
29 | this.timeout(60000) | 33 | this.timeout(60000) |
@@ -32,11 +36,7 @@ describe('Test stats (excluding redundancy)', function () { | |||
32 | 36 | ||
33 | await doubleFollow(servers[0], servers[1]) | 37 | await doubleFollow(servers[0], servers[1]) |
34 | 38 | ||
35 | const user = { | 39 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) |
36 | username: 'user1', | ||
37 | password: 'super_password' | ||
38 | } | ||
39 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) | ||
40 | 40 | ||
41 | const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) | 41 | const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) |
42 | const videoUUID = resVideo.body.video.uuid | 42 | const videoUUID = resVideo.body.video.uuid |
@@ -96,6 +96,40 @@ describe('Test stats (excluding redundancy)', function () { | |||
96 | expect(data.totalInstanceFollowers).to.equal(0) | 96 | expect(data.totalInstanceFollowers).to.equal(0) |
97 | }) | 97 | }) |
98 | 98 | ||
99 | it('Should have the correct total videos stats after an unfollow', async function () { | ||
100 | this.timeout(15000) | ||
101 | |||
102 | await unfollow(servers[2].url, servers[2].accessToken, servers[0]) | ||
103 | await waitJobs(servers) | ||
104 | |||
105 | const res = await getStats(servers[2].url) | ||
106 | const data: ServerStats = res.body | ||
107 | |||
108 | expect(data.totalVideos).to.equal(0) | ||
109 | }) | ||
110 | |||
111 | it('Should have the correct active users stats', async function () { | ||
112 | const server = servers[0] | ||
113 | |||
114 | { | ||
115 | const res = await getStats(server.url) | ||
116 | const data: ServerStats = res.body | ||
117 | expect(data.totalDailyActiveUsers).to.equal(1) | ||
118 | expect(data.totalWeeklyActiveUsers).to.equal(1) | ||
119 | expect(data.totalMonthlyActiveUsers).to.equal(1) | ||
120 | } | ||
121 | |||
122 | { | ||
123 | await userLogin(server, user) | ||
124 | |||
125 | const res = await getStats(server.url) | ||
126 | const data: ServerStats = res.body | ||
127 | expect(data.totalDailyActiveUsers).to.equal(2) | ||
128 | expect(data.totalWeeklyActiveUsers).to.equal(2) | ||
129 | expect(data.totalMonthlyActiveUsers).to.equal(2) | ||
130 | } | ||
131 | }) | ||
132 | |||
99 | after(async function () { | 133 | after(async function () { |
100 | await cleanupTests(servers) | 134 | await cleanupTests(servers) |
101 | }) | 135 | }) |
diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts index 9d7eec8ca..5b56a83bb 100644 --- a/server/tests/api/server/tracker.ts +++ b/server/tests/api/server/tracker.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await,@typescript-eslint/no-floating-promises */ |
2 | 2 | ||
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -49,7 +49,7 @@ describe('Test tracker', function () { | |||
49 | torrent.on('error', done) | 49 | torrent.on('error', done) |
50 | torrent.on('warning', warn => { | 50 | torrent.on('warning', warn => { |
51 | const message = typeof warn === 'string' ? warn : warn.message | 51 | const message = typeof warn === 'string' ? warn : warn.message |
52 | if (message.indexOf('Unknown infoHash ') !== -1) return done() | 52 | if (message.includes('Unknown infoHash ')) return done() |
53 | }) | 53 | }) |
54 | 54 | ||
55 | torrent.on('done', () => done(new Error('No error on infohash'))) | 55 | torrent.on('done', () => done(new Error('No error on infohash'))) |
@@ -64,7 +64,7 @@ describe('Test tracker', function () { | |||
64 | torrent.on('error', done) | 64 | torrent.on('error', done) |
65 | torrent.on('warning', warn => { | 65 | torrent.on('warning', warn => { |
66 | const message = typeof warn === 'string' ? warn : warn.message | 66 | const message = typeof warn === 'string' ? warn : warn.message |
67 | if (message.indexOf('Unknown infoHash ') !== -1) return done(new Error('Error on infohash')) | 67 | if (message.includes('Unknown infoHash ')) return done(new Error('Error on infohash')) |
68 | }) | 68 | }) |
69 | 69 | ||
70 | torrent.on('done', done) | 70 | torrent.on('done', done) |
@@ -73,6 +73,8 @@ describe('Test tracker', function () { | |||
73 | it('Should disable the tracker', function (done) { | 73 | it('Should disable the tracker', function (done) { |
74 | this.timeout(20000) | 74 | this.timeout(20000) |
75 | 75 | ||
76 | const errCb = () => done(new Error('Tracker is enabled')) | ||
77 | |||
76 | killallServers([ server ]) | 78 | killallServers([ server ]) |
77 | reRunServer(server, { tracker: { enabled: false } }) | 79 | reRunServer(server, { tracker: { enabled: false } }) |
78 | .then(() => { | 80 | .then(() => { |
@@ -83,10 +85,14 @@ describe('Test tracker', function () { | |||
83 | torrent.on('error', done) | 85 | torrent.on('error', done) |
84 | torrent.on('warning', warn => { | 86 | torrent.on('warning', warn => { |
85 | const message = typeof warn === 'string' ? warn : warn.message | 87 | const message = typeof warn === 'string' ? warn : warn.message |
86 | if (message.indexOf('disabled ') !== -1) return done() | 88 | if (message.includes('disabled ')) { |
89 | torrent.off('done', errCb) | ||
90 | |||
91 | return done() | ||
92 | } | ||
87 | }) | 93 | }) |
88 | 94 | ||
89 | torrent.on('done', () => done(new Error('Tracker is enabled'))) | 95 | torrent.on('done', errCb) |
90 | }) | 96 | }) |
91 | }) | 97 | }) |
92 | 98 | ||
diff --git a/server/tests/api/users/blocklist.ts b/server/tests/api/users/blocklist.ts index 05e58017a..21b9ae4f8 100644 --- a/server/tests/api/users/blocklist.ts +++ b/server/tests/api/users/blocklist.ts | |||
@@ -1,21 +1,20 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { AccountBlock, ServerBlock, UserNotificationType, Video } from '../../../../shared/index' | 5 | import { AccountBlock, ServerBlock, Video } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | cleanupTests, | 7 | cleanupTests, |
8 | createUser, deleteVideoComment, | 8 | createUser, |
9 | deleteVideoComment, | ||
9 | doubleFollow, | 10 | doubleFollow, |
10 | flushAndRunMultipleServers, | 11 | flushAndRunMultipleServers, |
11 | flushTests, | ||
12 | killallServers, | ||
13 | ServerInfo, | 12 | ServerInfo, |
14 | uploadVideo, | 13 | uploadVideo, |
15 | userLogin | 14 | userLogin |
16 | } from '../../../../shared/extra-utils/index' | 15 | } from '../../../../shared/extra-utils/index' |
17 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' | 16 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
18 | import { getVideosListWithToken, getVideosList } from '../../../../shared/extra-utils/videos/videos' | 17 | import { getVideosList, getVideosListWithToken } from '../../../../shared/extra-utils/videos/videos' |
19 | import { | 18 | import { |
20 | addVideoCommentReply, | 19 | addVideoCommentReply, |
21 | addVideoCommentThread, | 20 | addVideoCommentThread, |
@@ -79,7 +78,7 @@ async function checkCommentNotification ( | |||
79 | const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text) | 78 | const resComment = await addVideoCommentThread(comment.server.url, comment.token, comment.videoUUID, comment.text) |
80 | const threadId = resComment.body.comment.id | 79 | const threadId = resComment.body.comment.id |
81 | 80 | ||
82 | await waitJobs([ mainServer, comment.server]) | 81 | await waitJobs([ mainServer, comment.server ]) |
83 | 82 | ||
84 | const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30) | 83 | const res = await getUserNotifications(mainServer.url, mainServer.accessToken, 0, 30) |
85 | const commentNotifications = res.body.data | 84 | const commentNotifications = res.body.data |
@@ -90,7 +89,7 @@ async function checkCommentNotification ( | |||
90 | 89 | ||
91 | await deleteVideoComment(comment.server.url, comment.token, comment.videoUUID, threadId) | 90 | await deleteVideoComment(comment.server.url, comment.token, comment.videoUUID, threadId) |
92 | 91 | ||
93 | await waitJobs([ mainServer, comment.server]) | 92 | await waitJobs([ mainServer, comment.server ]) |
94 | } | 93 | } |
95 | 94 | ||
96 | describe('Test blocklist', function () { | 95 | describe('Test blocklist', function () { |
@@ -109,7 +108,7 @@ describe('Test blocklist', function () { | |||
109 | 108 | ||
110 | { | 109 | { |
111 | const user = { username: 'user1', password: 'password' } | 110 | const user = { username: 'user1', password: 'password' } |
112 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) | 111 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) |
113 | 112 | ||
114 | userToken1 = await userLogin(servers[0], user) | 113 | userToken1 = await userLogin(servers[0], user) |
115 | await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' }) | 114 | await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' }) |
@@ -117,14 +116,14 @@ describe('Test blocklist', function () { | |||
117 | 116 | ||
118 | { | 117 | { |
119 | const user = { username: 'moderator', password: 'password' } | 118 | const user = { username: 'moderator', password: 'password' } |
120 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) | 119 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) |
121 | 120 | ||
122 | userModeratorToken = await userLogin(servers[0], user) | 121 | userModeratorToken = await userLogin(servers[0], user) |
123 | } | 122 | } |
124 | 123 | ||
125 | { | 124 | { |
126 | const user = { username: 'user2', password: 'password' } | 125 | const user = { username: 'user2', password: 'password' } |
127 | await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) | 126 | await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password }) |
128 | 127 | ||
129 | userToken2 = await userLogin(servers[1], user) | 128 | userToken2 = await userLogin(servers[1], user) |
130 | await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' }) | 129 | await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' }) |
@@ -143,14 +142,14 @@ describe('Test blocklist', function () { | |||
143 | await doubleFollow(servers[0], servers[1]) | 142 | await doubleFollow(servers[0], servers[1]) |
144 | 143 | ||
145 | { | 144 | { |
146 | const resComment = await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, 'comment root 1') | 145 | const resComment = await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID1, 'comment root 1') |
147 | const resReply = await addVideoCommentReply(servers[ 0 ].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1') | 146 | const resReply = await addVideoCommentReply(servers[0].url, userToken1, videoUUID1, resComment.body.comment.id, 'comment user 1') |
148 | await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1') | 147 | await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID1, resReply.body.comment.id, 'comment root 1') |
149 | } | 148 | } |
150 | 149 | ||
151 | { | 150 | { |
152 | const resComment = await addVideoCommentThread(servers[ 0 ].url, userToken1, videoUUID1, 'comment user 1') | 151 | const resComment = await addVideoCommentThread(servers[0].url, userToken1, videoUUID1, 'comment user 1') |
153 | await addVideoCommentReply(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1') | 152 | await addVideoCommentReply(servers[0].url, servers[0].accessToken, videoUUID1, resComment.body.comment.id, 'comment root 1') |
154 | } | 153 | } |
155 | 154 | ||
156 | await waitJobs(servers) | 155 | await waitJobs(servers) |
@@ -160,19 +159,19 @@ describe('Test blocklist', function () { | |||
160 | 159 | ||
161 | describe('When managing account blocklist', function () { | 160 | describe('When managing account blocklist', function () { |
162 | it('Should list all videos', function () { | 161 | it('Should list all videos', function () { |
163 | return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) | 162 | return checkAllVideos(servers[0].url, servers[0].accessToken) |
164 | }) | 163 | }) |
165 | 164 | ||
166 | it('Should list the comments', function () { | 165 | it('Should list the comments', function () { |
167 | return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) | 166 | return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) |
168 | }) | 167 | }) |
169 | 168 | ||
170 | it('Should block a remote account', async function () { | 169 | it('Should block a remote account', async function () { |
171 | await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) | 170 | await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port) |
172 | }) | 171 | }) |
173 | 172 | ||
174 | it('Should hide its videos', async function () { | 173 | it('Should hide its videos', async function () { |
175 | const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) | 174 | const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) |
176 | 175 | ||
177 | const videos: Video[] = res.body.data | 176 | const videos: Video[] = res.body.data |
178 | expect(videos).to.have.lengthOf(3) | 177 | expect(videos).to.have.lengthOf(3) |
@@ -182,11 +181,11 @@ describe('Test blocklist', function () { | |||
182 | }) | 181 | }) |
183 | 182 | ||
184 | it('Should block a local account', async function () { | 183 | it('Should block a local account', async function () { |
185 | await addAccountToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') | 184 | await addAccountToAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1') |
186 | }) | 185 | }) |
187 | 186 | ||
188 | it('Should hide its videos', async function () { | 187 | it('Should hide its videos', async function () { |
189 | const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) | 188 | const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) |
190 | 189 | ||
191 | const videos: Video[] = res.body.data | 190 | const videos: Video[] = res.body.data |
192 | expect(videos).to.have.lengthOf(2) | 191 | expect(videos).to.have.lengthOf(2) |
@@ -196,17 +195,17 @@ describe('Test blocklist', function () { | |||
196 | }) | 195 | }) |
197 | 196 | ||
198 | it('Should hide its comments', async function () { | 197 | it('Should hide its comments', async function () { |
199 | const resThreads = await getVideoCommentThreads(servers[ 0 ].url, videoUUID1, 0, 5, '-createdAt', servers[ 0 ].accessToken) | 198 | const resThreads = await getVideoCommentThreads(servers[0].url, videoUUID1, 0, 5, '-createdAt', servers[0].accessToken) |
200 | 199 | ||
201 | const threads: VideoComment[] = resThreads.body.data | 200 | const threads: VideoComment[] = resThreads.body.data |
202 | expect(threads).to.have.lengthOf(1) | 201 | expect(threads).to.have.lengthOf(1) |
203 | expect(threads[ 0 ].totalReplies).to.equal(0) | 202 | expect(threads[0].totalReplies).to.equal(0) |
204 | 203 | ||
205 | const t = threads.find(t => t.text === 'comment user 1') | 204 | const t = threads.find(t => t.text === 'comment user 1') |
206 | expect(t).to.be.undefined | 205 | expect(t).to.be.undefined |
207 | 206 | ||
208 | for (const thread of threads) { | 207 | for (const thread of threads) { |
209 | const res = await getVideoThreadComments(servers[ 0 ].url, videoUUID1, thread.id, servers[ 0 ].accessToken) | 208 | const res = await getVideoThreadComments(servers[0].url, videoUUID1, thread.id, servers[0].accessToken) |
210 | 209 | ||
211 | const tree: VideoCommentThreadTree = res.body | 210 | const tree: VideoCommentThreadTree = res.body |
212 | expect(tree.children).to.have.lengthOf(0) | 211 | expect(tree.children).to.have.lengthOf(0) |
@@ -217,37 +216,37 @@ describe('Test blocklist', function () { | |||
217 | this.timeout(20000) | 216 | this.timeout(20000) |
218 | 217 | ||
219 | { | 218 | { |
220 | const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' } | 219 | const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' } |
221 | await checkCommentNotification(servers[ 0 ], comment, 'absence') | 220 | await checkCommentNotification(servers[0], comment, 'absence') |
222 | } | 221 | } |
223 | 222 | ||
224 | { | 223 | { |
225 | const comment = { | 224 | const comment = { |
226 | server: servers[ 0 ], | 225 | server: servers[0], |
227 | token: userToken1, | 226 | token: userToken1, |
228 | videoUUID: videoUUID2, | 227 | videoUUID: videoUUID2, |
229 | text: 'hello @root@localhost:' + servers[ 0 ].port | 228 | text: 'hello @root@localhost:' + servers[0].port |
230 | } | 229 | } |
231 | await checkCommentNotification(servers[ 0 ], comment, 'absence') | 230 | await checkCommentNotification(servers[0], comment, 'absence') |
232 | } | 231 | } |
233 | }) | 232 | }) |
234 | 233 | ||
235 | it('Should list all the videos with another user', async function () { | 234 | it('Should list all the videos with another user', async function () { |
236 | return checkAllVideos(servers[ 0 ].url, userToken1) | 235 | return checkAllVideos(servers[0].url, userToken1) |
237 | }) | 236 | }) |
238 | 237 | ||
239 | it('Should list all the comments with another user', async function () { | 238 | it('Should list all the comments with another user', async function () { |
240 | return checkAllComments(servers[ 0 ].url, userToken1, videoUUID1) | 239 | return checkAllComments(servers[0].url, userToken1, videoUUID1) |
241 | }) | 240 | }) |
242 | 241 | ||
243 | it('Should list blocked accounts', async function () { | 242 | it('Should list blocked accounts', async function () { |
244 | { | 243 | { |
245 | const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') | 244 | const res = await getAccountBlocklistByAccount(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt') |
246 | const blocks: AccountBlock[] = res.body.data | 245 | const blocks: AccountBlock[] = res.body.data |
247 | 246 | ||
248 | expect(res.body.total).to.equal(2) | 247 | expect(res.body.total).to.equal(2) |
249 | 248 | ||
250 | const block = blocks[ 0 ] | 249 | const block = blocks[0] |
251 | expect(block.byAccount.displayName).to.equal('root') | 250 | expect(block.byAccount.displayName).to.equal('root') |
252 | expect(block.byAccount.name).to.equal('root') | 251 | expect(block.byAccount.name).to.equal('root') |
253 | expect(block.blockedAccount.displayName).to.equal('user2') | 252 | expect(block.blockedAccount.displayName).to.equal('user2') |
@@ -256,12 +255,12 @@ describe('Test blocklist', function () { | |||
256 | } | 255 | } |
257 | 256 | ||
258 | { | 257 | { |
259 | const res = await getAccountBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') | 258 | const res = await getAccountBlocklistByAccount(servers[0].url, servers[0].accessToken, 1, 2, 'createdAt') |
260 | const blocks: AccountBlock[] = res.body.data | 259 | const blocks: AccountBlock[] = res.body.data |
261 | 260 | ||
262 | expect(res.body.total).to.equal(2) | 261 | expect(res.body.total).to.equal(2) |
263 | 262 | ||
264 | const block = blocks[ 0 ] | 263 | const block = blocks[0] |
265 | expect(block.byAccount.displayName).to.equal('root') | 264 | expect(block.byAccount.displayName).to.equal('root') |
266 | expect(block.byAccount.name).to.equal('root') | 265 | expect(block.byAccount.name).to.equal('root') |
267 | expect(block.blockedAccount.displayName).to.equal('user1') | 266 | expect(block.blockedAccount.displayName).to.equal('user1') |
@@ -271,11 +270,11 @@ describe('Test blocklist', function () { | |||
271 | }) | 270 | }) |
272 | 271 | ||
273 | it('Should unblock the remote account', async function () { | 272 | it('Should unblock the remote account', async function () { |
274 | await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) | 273 | await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port) |
275 | }) | 274 | }) |
276 | 275 | ||
277 | it('Should display its videos', async function () { | 276 | it('Should display its videos', async function () { |
278 | const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) | 277 | const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) |
279 | 278 | ||
280 | const videos: Video[] = res.body.data | 279 | const videos: Video[] = res.body.data |
281 | expect(videos).to.have.lengthOf(3) | 280 | expect(videos).to.have.lengthOf(3) |
@@ -285,48 +284,48 @@ describe('Test blocklist', function () { | |||
285 | }) | 284 | }) |
286 | 285 | ||
287 | it('Should unblock the local account', async function () { | 286 | it('Should unblock the local account', async function () { |
288 | await removeAccountFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') | 287 | await removeAccountFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'user1') |
289 | }) | 288 | }) |
290 | 289 | ||
291 | it('Should display its comments', function () { | 290 | it('Should display its comments', function () { |
292 | return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) | 291 | return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) |
293 | }) | 292 | }) |
294 | 293 | ||
295 | it('Should have a notification from a non blocked account', async function () { | 294 | it('Should have a notification from a non blocked account', async function () { |
296 | this.timeout(20000) | 295 | this.timeout(20000) |
297 | 296 | ||
298 | { | 297 | { |
299 | const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } | 298 | const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } |
300 | await checkCommentNotification(servers[ 0 ], comment, 'presence') | 299 | await checkCommentNotification(servers[0], comment, 'presence') |
301 | } | 300 | } |
302 | 301 | ||
303 | { | 302 | { |
304 | const comment = { | 303 | const comment = { |
305 | server: servers[ 0 ], | 304 | server: servers[0], |
306 | token: userToken1, | 305 | token: userToken1, |
307 | videoUUID: videoUUID2, | 306 | videoUUID: videoUUID2, |
308 | text: 'hello @root@localhost:' + servers[ 0 ].port | 307 | text: 'hello @root@localhost:' + servers[0].port |
309 | } | 308 | } |
310 | await checkCommentNotification(servers[ 0 ], comment, 'presence') | 309 | await checkCommentNotification(servers[0], comment, 'presence') |
311 | } | 310 | } |
312 | }) | 311 | }) |
313 | }) | 312 | }) |
314 | 313 | ||
315 | describe('When managing server blocklist', function () { | 314 | describe('When managing server blocklist', function () { |
316 | it('Should list all videos', function () { | 315 | it('Should list all videos', function () { |
317 | return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) | 316 | return checkAllVideos(servers[0].url, servers[0].accessToken) |
318 | }) | 317 | }) |
319 | 318 | ||
320 | it('Should list the comments', function () { | 319 | it('Should list the comments', function () { |
321 | return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) | 320 | return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) |
322 | }) | 321 | }) |
323 | 322 | ||
324 | it('Should block a remote server', async function () { | 323 | it('Should block a remote server', async function () { |
325 | await addServerToAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) | 324 | await addServerToAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) |
326 | }) | 325 | }) |
327 | 326 | ||
328 | it('Should hide its videos', async function () { | 327 | it('Should hide its videos', async function () { |
329 | const res = await getVideosListWithToken(servers[ 0 ].url, servers[ 0 ].accessToken) | 328 | const res = await getVideosListWithToken(servers[0].url, servers[0].accessToken) |
330 | 329 | ||
331 | const videos: Video[] = res.body.data | 330 | const videos: Video[] = res.body.data |
332 | expect(videos).to.have.lengthOf(2) | 331 | expect(videos).to.have.lengthOf(2) |
@@ -339,81 +338,81 @@ describe('Test blocklist', function () { | |||
339 | }) | 338 | }) |
340 | 339 | ||
341 | it('Should list all the videos with another user', async function () { | 340 | it('Should list all the videos with another user', async function () { |
342 | return checkAllVideos(servers[ 0 ].url, userToken1) | 341 | return checkAllVideos(servers[0].url, userToken1) |
343 | }) | 342 | }) |
344 | 343 | ||
345 | it('Should hide its comments', async function () { | 344 | it('Should hide its comments', async function () { |
346 | this.timeout(10000) | 345 | this.timeout(10000) |
347 | 346 | ||
348 | const resThreads = await addVideoCommentThread(servers[ 1 ].url, userToken2, videoUUID1, 'hidden comment 2') | 347 | const resThreads = await addVideoCommentThread(servers[1].url, userToken2, videoUUID1, 'hidden comment 2') |
349 | const threadId = resThreads.body.comment.id | 348 | const threadId = resThreads.body.comment.id |
350 | 349 | ||
351 | await waitJobs(servers) | 350 | await waitJobs(servers) |
352 | 351 | ||
353 | await checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) | 352 | await checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) |
354 | 353 | ||
355 | await deleteVideoComment(servers[ 1 ].url, userToken2, videoUUID1, threadId) | 354 | await deleteVideoComment(servers[1].url, userToken2, videoUUID1, threadId) |
356 | }) | 355 | }) |
357 | 356 | ||
358 | it('Should not have notifications from blocked server', async function () { | 357 | it('Should not have notifications from blocked server', async function () { |
359 | this.timeout(20000) | 358 | this.timeout(20000) |
360 | 359 | ||
361 | { | 360 | { |
362 | const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } | 361 | const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } |
363 | await checkCommentNotification(servers[ 0 ], comment, 'absence') | 362 | await checkCommentNotification(servers[0], comment, 'absence') |
364 | } | 363 | } |
365 | 364 | ||
366 | { | 365 | { |
367 | const comment = { | 366 | const comment = { |
368 | server: servers[ 1 ], | 367 | server: servers[1], |
369 | token: userToken2, | 368 | token: userToken2, |
370 | videoUUID: videoUUID1, | 369 | videoUUID: videoUUID1, |
371 | text: 'hello @root@localhost:' + servers[ 0 ].port | 370 | text: 'hello @root@localhost:' + servers[0].port |
372 | } | 371 | } |
373 | await checkCommentNotification(servers[ 0 ], comment, 'absence') | 372 | await checkCommentNotification(servers[0], comment, 'absence') |
374 | } | 373 | } |
375 | }) | 374 | }) |
376 | 375 | ||
377 | it('Should list blocked servers', async function () { | 376 | it('Should list blocked servers', async function () { |
378 | const res = await getServerBlocklistByAccount(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') | 377 | const res = await getServerBlocklistByAccount(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt') |
379 | const blocks: ServerBlock[] = res.body.data | 378 | const blocks: ServerBlock[] = res.body.data |
380 | 379 | ||
381 | expect(res.body.total).to.equal(1) | 380 | expect(res.body.total).to.equal(1) |
382 | 381 | ||
383 | const block = blocks[ 0 ] | 382 | const block = blocks[0] |
384 | expect(block.byAccount.displayName).to.equal('root') | 383 | expect(block.byAccount.displayName).to.equal('root') |
385 | expect(block.byAccount.name).to.equal('root') | 384 | expect(block.byAccount.name).to.equal('root') |
386 | expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) | 385 | expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) |
387 | }) | 386 | }) |
388 | 387 | ||
389 | it('Should unblock the remote server', async function () { | 388 | it('Should unblock the remote server', async function () { |
390 | await removeServerFromAccountBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) | 389 | await removeServerFromAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) |
391 | }) | 390 | }) |
392 | 391 | ||
393 | it('Should display its videos', function () { | 392 | it('Should display its videos', function () { |
394 | return checkAllVideos(servers[ 0 ].url, servers[ 0 ].accessToken) | 393 | return checkAllVideos(servers[0].url, servers[0].accessToken) |
395 | }) | 394 | }) |
396 | 395 | ||
397 | it('Should display its comments', function () { | 396 | it('Should display its comments', function () { |
398 | return checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) | 397 | return checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) |
399 | }) | 398 | }) |
400 | 399 | ||
401 | it('Should have notification from unblocked server', async function () { | 400 | it('Should have notification from unblocked server', async function () { |
402 | this.timeout(20000) | 401 | this.timeout(20000) |
403 | 402 | ||
404 | { | 403 | { |
405 | const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } | 404 | const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } |
406 | await checkCommentNotification(servers[ 0 ], comment, 'presence') | 405 | await checkCommentNotification(servers[0], comment, 'presence') |
407 | } | 406 | } |
408 | 407 | ||
409 | { | 408 | { |
410 | const comment = { | 409 | const comment = { |
411 | server: servers[ 1 ], | 410 | server: servers[1], |
412 | token: userToken2, | 411 | token: userToken2, |
413 | videoUUID: videoUUID1, | 412 | videoUUID: videoUUID1, |
414 | text: 'hello @root@localhost:' + servers[ 0 ].port | 413 | text: 'hello @root@localhost:' + servers[0].port |
415 | } | 414 | } |
416 | await checkCommentNotification(servers[ 0 ], comment, 'presence') | 415 | await checkCommentNotification(servers[0], comment, 'presence') |
417 | } | 416 | } |
418 | }) | 417 | }) |
419 | }) | 418 | }) |
@@ -423,24 +422,24 @@ describe('Test blocklist', function () { | |||
423 | 422 | ||
424 | describe('When managing account blocklist', function () { | 423 | describe('When managing account blocklist', function () { |
425 | it('Should list all videos', async function () { | 424 | it('Should list all videos', async function () { |
426 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 425 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
427 | await checkAllVideos(servers[ 0 ].url, token) | 426 | await checkAllVideos(servers[0].url, token) |
428 | } | 427 | } |
429 | }) | 428 | }) |
430 | 429 | ||
431 | it('Should list the comments', async function () { | 430 | it('Should list the comments', async function () { |
432 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 431 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
433 | await checkAllComments(servers[ 0 ].url, token, videoUUID1) | 432 | await checkAllComments(servers[0].url, token, videoUUID1) |
434 | } | 433 | } |
435 | }) | 434 | }) |
436 | 435 | ||
437 | it('Should block a remote account', async function () { | 436 | it('Should block a remote account', async function () { |
438 | await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) | 437 | await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port) |
439 | }) | 438 | }) |
440 | 439 | ||
441 | it('Should hide its videos', async function () { | 440 | it('Should hide its videos', async function () { |
442 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 441 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
443 | const res = await getVideosListWithToken(servers[ 0 ].url, token) | 442 | const res = await getVideosListWithToken(servers[0].url, token) |
444 | 443 | ||
445 | const videos: Video[] = res.body.data | 444 | const videos: Video[] = res.body.data |
446 | expect(videos).to.have.lengthOf(3) | 445 | expect(videos).to.have.lengthOf(3) |
@@ -451,12 +450,12 @@ describe('Test blocklist', function () { | |||
451 | }) | 450 | }) |
452 | 451 | ||
453 | it('Should block a local account', async function () { | 452 | it('Should block a local account', async function () { |
454 | await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') | 453 | await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'user1') |
455 | }) | 454 | }) |
456 | 455 | ||
457 | it('Should hide its videos', async function () { | 456 | it('Should hide its videos', async function () { |
458 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 457 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
459 | const res = await getVideosListWithToken(servers[ 0 ].url, token) | 458 | const res = await getVideosListWithToken(servers[0].url, token) |
460 | 459 | ||
461 | const videos: Video[] = res.body.data | 460 | const videos: Video[] = res.body.data |
462 | expect(videos).to.have.lengthOf(2) | 461 | expect(videos).to.have.lengthOf(2) |
@@ -467,18 +466,18 @@ describe('Test blocklist', function () { | |||
467 | }) | 466 | }) |
468 | 467 | ||
469 | it('Should hide its comments', async function () { | 468 | it('Should hide its comments', async function () { |
470 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 469 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
471 | const resThreads = await getVideoCommentThreads(servers[ 0 ].url, videoUUID1, 0, 5, '-createdAt', token) | 470 | const resThreads = await getVideoCommentThreads(servers[0].url, videoUUID1, 0, 5, '-createdAt', token) |
472 | 471 | ||
473 | const threads: VideoComment[] = resThreads.body.data | 472 | const threads: VideoComment[] = resThreads.body.data |
474 | expect(threads).to.have.lengthOf(1) | 473 | expect(threads).to.have.lengthOf(1) |
475 | expect(threads[ 0 ].totalReplies).to.equal(0) | 474 | expect(threads[0].totalReplies).to.equal(0) |
476 | 475 | ||
477 | const t = threads.find(t => t.text === 'comment user 1') | 476 | const t = threads.find(t => t.text === 'comment user 1') |
478 | expect(t).to.be.undefined | 477 | expect(t).to.be.undefined |
479 | 478 | ||
480 | for (const thread of threads) { | 479 | for (const thread of threads) { |
481 | const res = await getVideoThreadComments(servers[ 0 ].url, videoUUID1, thread.id, token) | 480 | const res = await getVideoThreadComments(servers[0].url, videoUUID1, thread.id, token) |
482 | 481 | ||
483 | const tree: VideoCommentThreadTree = res.body | 482 | const tree: VideoCommentThreadTree = res.body |
484 | expect(tree.children).to.have.lengthOf(0) | 483 | expect(tree.children).to.have.lengthOf(0) |
@@ -490,29 +489,29 @@ describe('Test blocklist', function () { | |||
490 | this.timeout(20000) | 489 | this.timeout(20000) |
491 | 490 | ||
492 | { | 491 | { |
493 | const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' } | 492 | const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'hidden comment' } |
494 | await checkCommentNotification(servers[ 0 ], comment, 'absence') | 493 | await checkCommentNotification(servers[0], comment, 'absence') |
495 | } | 494 | } |
496 | 495 | ||
497 | { | 496 | { |
498 | const comment = { | 497 | const comment = { |
499 | server: servers[ 1 ], | 498 | server: servers[1], |
500 | token: userToken2, | 499 | token: userToken2, |
501 | videoUUID: videoUUID1, | 500 | videoUUID: videoUUID1, |
502 | text: 'hello @root@localhost:' + servers[ 0 ].port | 501 | text: 'hello @root@localhost:' + servers[0].port |
503 | } | 502 | } |
504 | await checkCommentNotification(servers[ 0 ], comment, 'absence') | 503 | await checkCommentNotification(servers[0], comment, 'absence') |
505 | } | 504 | } |
506 | }) | 505 | }) |
507 | 506 | ||
508 | it('Should list blocked accounts', async function () { | 507 | it('Should list blocked accounts', async function () { |
509 | { | 508 | { |
510 | const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') | 509 | const res = await getAccountBlocklistByServer(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt') |
511 | const blocks: AccountBlock[] = res.body.data | 510 | const blocks: AccountBlock[] = res.body.data |
512 | 511 | ||
513 | expect(res.body.total).to.equal(2) | 512 | expect(res.body.total).to.equal(2) |
514 | 513 | ||
515 | const block = blocks[ 0 ] | 514 | const block = blocks[0] |
516 | expect(block.byAccount.displayName).to.equal('peertube') | 515 | expect(block.byAccount.displayName).to.equal('peertube') |
517 | expect(block.byAccount.name).to.equal('peertube') | 516 | expect(block.byAccount.name).to.equal('peertube') |
518 | expect(block.blockedAccount.displayName).to.equal('user2') | 517 | expect(block.blockedAccount.displayName).to.equal('user2') |
@@ -521,12 +520,12 @@ describe('Test blocklist', function () { | |||
521 | } | 520 | } |
522 | 521 | ||
523 | { | 522 | { |
524 | const res = await getAccountBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 1, 2, 'createdAt') | 523 | const res = await getAccountBlocklistByServer(servers[0].url, servers[0].accessToken, 1, 2, 'createdAt') |
525 | const blocks: AccountBlock[] = res.body.data | 524 | const blocks: AccountBlock[] = res.body.data |
526 | 525 | ||
527 | expect(res.body.total).to.equal(2) | 526 | expect(res.body.total).to.equal(2) |
528 | 527 | ||
529 | const block = blocks[ 0 ] | 528 | const block = blocks[0] |
530 | expect(block.byAccount.displayName).to.equal('peertube') | 529 | expect(block.byAccount.displayName).to.equal('peertube') |
531 | expect(block.byAccount.name).to.equal('peertube') | 530 | expect(block.byAccount.name).to.equal('peertube') |
532 | expect(block.blockedAccount.displayName).to.equal('user1') | 531 | expect(block.blockedAccount.displayName).to.equal('user1') |
@@ -536,12 +535,12 @@ describe('Test blocklist', function () { | |||
536 | }) | 535 | }) |
537 | 536 | ||
538 | it('Should unblock the remote account', async function () { | 537 | it('Should unblock the remote account', async function () { |
539 | await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user2@localhost:' + servers[1].port) | 538 | await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'user2@localhost:' + servers[1].port) |
540 | }) | 539 | }) |
541 | 540 | ||
542 | it('Should display its videos', async function () { | 541 | it('Should display its videos', async function () { |
543 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 542 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
544 | const res = await getVideosListWithToken(servers[ 0 ].url, token) | 543 | const res = await getVideosListWithToken(servers[0].url, token) |
545 | 544 | ||
546 | const videos: Video[] = res.body.data | 545 | const videos: Video[] = res.body.data |
547 | expect(videos).to.have.lengthOf(3) | 546 | expect(videos).to.have.lengthOf(3) |
@@ -552,12 +551,12 @@ describe('Test blocklist', function () { | |||
552 | }) | 551 | }) |
553 | 552 | ||
554 | it('Should unblock the local account', async function () { | 553 | it('Should unblock the local account', async function () { |
555 | await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'user1') | 554 | await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'user1') |
556 | }) | 555 | }) |
557 | 556 | ||
558 | it('Should display its comments', async function () { | 557 | it('Should display its comments', async function () { |
559 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 558 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
560 | await checkAllComments(servers[ 0 ].url, token, videoUUID1) | 559 | await checkAllComments(servers[0].url, token, videoUUID1) |
561 | } | 560 | } |
562 | }) | 561 | }) |
563 | 562 | ||
@@ -565,43 +564,43 @@ describe('Test blocklist', function () { | |||
565 | this.timeout(20000) | 564 | this.timeout(20000) |
566 | 565 | ||
567 | { | 566 | { |
568 | const comment = { server: servers[ 0 ], token: userToken1, videoUUID: videoUUID1, text: 'displayed comment' } | 567 | const comment = { server: servers[0], token: userToken1, videoUUID: videoUUID1, text: 'displayed comment' } |
569 | await checkCommentNotification(servers[ 0 ], comment, 'presence') | 568 | await checkCommentNotification(servers[0], comment, 'presence') |
570 | } | 569 | } |
571 | 570 | ||
572 | { | 571 | { |
573 | const comment = { | 572 | const comment = { |
574 | server: servers[ 1 ], | 573 | server: servers[1], |
575 | token: userToken2, | 574 | token: userToken2, |
576 | videoUUID: videoUUID1, | 575 | videoUUID: videoUUID1, |
577 | text: 'hello @root@localhost:' + servers[ 0 ].port | 576 | text: 'hello @root@localhost:' + servers[0].port |
578 | } | 577 | } |
579 | await checkCommentNotification(servers[ 0 ], comment, 'presence') | 578 | await checkCommentNotification(servers[0], comment, 'presence') |
580 | } | 579 | } |
581 | }) | 580 | }) |
582 | }) | 581 | }) |
583 | 582 | ||
584 | describe('When managing server blocklist', function () { | 583 | describe('When managing server blocklist', function () { |
585 | it('Should list all videos', async function () { | 584 | it('Should list all videos', async function () { |
586 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 585 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
587 | await checkAllVideos(servers[ 0 ].url, token) | 586 | await checkAllVideos(servers[0].url, token) |
588 | } | 587 | } |
589 | }) | 588 | }) |
590 | 589 | ||
591 | it('Should list the comments', async function () { | 590 | it('Should list the comments', async function () { |
592 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 591 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
593 | await checkAllComments(servers[ 0 ].url, token, videoUUID1) | 592 | await checkAllComments(servers[0].url, token, videoUUID1) |
594 | } | 593 | } |
595 | }) | 594 | }) |
596 | 595 | ||
597 | it('Should block a remote server', async function () { | 596 | it('Should block a remote server', async function () { |
598 | await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) | 597 | await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) |
599 | }) | 598 | }) |
600 | 599 | ||
601 | it('Should hide its videos', async function () { | 600 | it('Should hide its videos', async function () { |
602 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 601 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
603 | const res1 = await getVideosList(servers[ 0 ].url) | 602 | const res1 = await getVideosList(servers[0].url) |
604 | const res2 = await getVideosListWithToken(servers[ 0 ].url, token) | 603 | const res2 = await getVideosListWithToken(servers[0].url, token) |
605 | 604 | ||
606 | for (const res of [ res1, res2 ]) { | 605 | for (const res of [ res1, res2 ]) { |
607 | const videos: Video[] = res.body.data | 606 | const videos: Video[] = res.body.data |
@@ -619,60 +618,60 @@ describe('Test blocklist', function () { | |||
619 | it('Should hide its comments', async function () { | 618 | it('Should hide its comments', async function () { |
620 | this.timeout(10000) | 619 | this.timeout(10000) |
621 | 620 | ||
622 | const resThreads = await addVideoCommentThread(servers[ 1 ].url, userToken2, videoUUID1, 'hidden comment 2') | 621 | const resThreads = await addVideoCommentThread(servers[1].url, userToken2, videoUUID1, 'hidden comment 2') |
623 | const threadId = resThreads.body.comment.id | 622 | const threadId = resThreads.body.comment.id |
624 | 623 | ||
625 | await waitJobs(servers) | 624 | await waitJobs(servers) |
626 | 625 | ||
627 | await checkAllComments(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID1) | 626 | await checkAllComments(servers[0].url, servers[0].accessToken, videoUUID1) |
628 | 627 | ||
629 | await deleteVideoComment(servers[ 1 ].url, userToken2, videoUUID1, threadId) | 628 | await deleteVideoComment(servers[1].url, userToken2, videoUUID1, threadId) |
630 | }) | 629 | }) |
631 | 630 | ||
632 | it('Should not have notification from blocked instances by instance', async function () { | 631 | it('Should not have notification from blocked instances by instance', async function () { |
633 | this.timeout(20000) | 632 | this.timeout(20000) |
634 | 633 | ||
635 | { | 634 | { |
636 | const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } | 635 | const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'hidden comment' } |
637 | await checkCommentNotification(servers[ 0 ], comment, 'absence') | 636 | await checkCommentNotification(servers[0], comment, 'absence') |
638 | } | 637 | } |
639 | 638 | ||
640 | { | 639 | { |
641 | const comment = { | 640 | const comment = { |
642 | server: servers[ 1 ], | 641 | server: servers[1], |
643 | token: userToken2, | 642 | token: userToken2, |
644 | videoUUID: videoUUID1, | 643 | videoUUID: videoUUID1, |
645 | text: 'hello @root@localhost:' + servers[ 0 ].port | 644 | text: 'hello @root@localhost:' + servers[0].port |
646 | } | 645 | } |
647 | await checkCommentNotification(servers[ 0 ], comment, 'absence') | 646 | await checkCommentNotification(servers[0], comment, 'absence') |
648 | } | 647 | } |
649 | }) | 648 | }) |
650 | 649 | ||
651 | it('Should list blocked servers', async function () { | 650 | it('Should list blocked servers', async function () { |
652 | const res = await getServerBlocklistByServer(servers[ 0 ].url, servers[ 0 ].accessToken, 0, 1, 'createdAt') | 651 | const res = await getServerBlocklistByServer(servers[0].url, servers[0].accessToken, 0, 1, 'createdAt') |
653 | const blocks: ServerBlock[] = res.body.data | 652 | const blocks: ServerBlock[] = res.body.data |
654 | 653 | ||
655 | expect(res.body.total).to.equal(1) | 654 | expect(res.body.total).to.equal(1) |
656 | 655 | ||
657 | const block = blocks[ 0 ] | 656 | const block = blocks[0] |
658 | expect(block.byAccount.displayName).to.equal('peertube') | 657 | expect(block.byAccount.displayName).to.equal('peertube') |
659 | expect(block.byAccount.name).to.equal('peertube') | 658 | expect(block.byAccount.name).to.equal('peertube') |
660 | expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) | 659 | expect(block.blockedServer.host).to.equal('localhost:' + servers[1].port) |
661 | }) | 660 | }) |
662 | 661 | ||
663 | it('Should unblock the remote server', async function () { | 662 | it('Should unblock the remote server', async function () { |
664 | await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) | 663 | await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) |
665 | }) | 664 | }) |
666 | 665 | ||
667 | it('Should list all videos', async function () { | 666 | it('Should list all videos', async function () { |
668 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 667 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
669 | await checkAllVideos(servers[ 0 ].url, token) | 668 | await checkAllVideos(servers[0].url, token) |
670 | } | 669 | } |
671 | }) | 670 | }) |
672 | 671 | ||
673 | it('Should list the comments', async function () { | 672 | it('Should list the comments', async function () { |
674 | for (const token of [ userModeratorToken, servers[ 0 ].accessToken ]) { | 673 | for (const token of [ userModeratorToken, servers[0].accessToken ]) { |
675 | await checkAllComments(servers[ 0 ].url, token, videoUUID1) | 674 | await checkAllComments(servers[0].url, token, videoUUID1) |
676 | } | 675 | } |
677 | }) | 676 | }) |
678 | 677 | ||
@@ -680,18 +679,18 @@ describe('Test blocklist', function () { | |||
680 | this.timeout(20000) | 679 | this.timeout(20000) |
681 | 680 | ||
682 | { | 681 | { |
683 | const comment = { server: servers[ 1 ], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } | 682 | const comment = { server: servers[1], token: userToken2, videoUUID: videoUUID1, text: 'displayed comment' } |
684 | await checkCommentNotification(servers[ 0 ], comment, 'presence') | 683 | await checkCommentNotification(servers[0], comment, 'presence') |
685 | } | 684 | } |
686 | 685 | ||
687 | { | 686 | { |
688 | const comment = { | 687 | const comment = { |
689 | server: servers[ 1 ], | 688 | server: servers[1], |
690 | token: userToken2, | 689 | token: userToken2, |
691 | videoUUID: videoUUID1, | 690 | videoUUID: videoUUID1, |
692 | text: 'hello @root@localhost:' + servers[ 0 ].port | 691 | text: 'hello @root@localhost:' + servers[0].port |
693 | } | 692 | } |
694 | await checkCommentNotification(servers[ 0 ], comment, 'presence') | 693 | await checkCommentNotification(servers[0], comment, 'presence') |
695 | } | 694 | } |
696 | }) | 695 | }) |
697 | }) | 696 | }) |
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts index 08017f89c..7d6b0c6a9 100644 --- a/server/tests/api/users/user-subscriptions.ts +++ b/server/tests/api/users/user-subscriptions.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -13,16 +13,17 @@ import { | |||
13 | updateVideo, | 13 | updateVideo, |
14 | userLogin | 14 | userLogin |
15 | } from '../../../../shared/extra-utils' | 15 | } from '../../../../shared/extra-utils' |
16 | import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index' | 16 | import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index' |
17 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' | 17 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
18 | import { Video, VideoChannel } from '../../../../shared/models/videos' | 18 | import { Video, VideoChannel } from '../../../../shared/models/videos' |
19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
20 | import { | 20 | import { |
21 | addUserSubscription, | 21 | addUserSubscription, |
22 | areSubscriptionsExist, | ||
23 | getUserSubscription, | ||
22 | listUserSubscriptions, | 24 | listUserSubscriptions, |
23 | listUserSubscriptionVideos, | 25 | listUserSubscriptionVideos, |
24 | removeUserSubscription, | 26 | removeUserSubscription |
25 | getUserSubscription, areSubscriptionsExist | ||
26 | } from '../../../../shared/extra-utils/users/user-subscriptions' | 27 | } from '../../../../shared/extra-utils/users/user-subscriptions' |
27 | 28 | ||
28 | const expect = chai.expect | 29 | const expect = chai.expect |
@@ -116,7 +117,7 @@ describe('Test users subscriptions', function () { | |||
116 | 117 | ||
117 | it('Should get subscription', async function () { | 118 | it('Should get subscription', async function () { |
118 | { | 119 | { |
119 | const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'user3_channel@localhost:' + servers[2].port) | 120 | const res = await getUserSubscription(servers[0].url, users[0].accessToken, 'user3_channel@localhost:' + servers[2].port) |
120 | const videoChannel: VideoChannel = res.body | 121 | const videoChannel: VideoChannel = res.body |
121 | 122 | ||
122 | expect(videoChannel.name).to.equal('user3_channel') | 123 | expect(videoChannel.name).to.equal('user3_channel') |
@@ -127,7 +128,7 @@ describe('Test users subscriptions', function () { | |||
127 | } | 128 | } |
128 | 129 | ||
129 | { | 130 | { |
130 | const res = await getUserSubscription(servers[ 0 ].url, users[ 0 ].accessToken, 'root_channel@localhost:' + servers[0].port) | 131 | const res = await getUserSubscription(servers[0].url, users[0].accessToken, 'root_channel@localhost:' + servers[0].port) |
131 | const videoChannel: VideoChannel = res.body | 132 | const videoChannel: VideoChannel = res.body |
132 | 133 | ||
133 | expect(videoChannel.name).to.equal('root_channel') | 134 | expect(videoChannel.name).to.equal('root_channel') |
@@ -146,7 +147,7 @@ describe('Test users subscriptions', function () { | |||
146 | 'user3_channel@localhost:' + servers[0].port | 147 | 'user3_channel@localhost:' + servers[0].port |
147 | ] | 148 | ] |
148 | 149 | ||
149 | const res = await areSubscriptionsExist(servers[ 0 ].url, users[ 0 ].accessToken, uris) | 150 | const res = await areSubscriptionsExist(servers[0].url, users[0].accessToken, uris) |
150 | const body = res.body | 151 | const body = res.body |
151 | 152 | ||
152 | expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true | 153 | expect(body['user3_channel@localhost:' + servers[2].port]).to.be.true |
diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts index 791418318..591ce4959 100644 --- a/server/tests/api/users/users-multiple-servers.ts +++ b/server/tests/api/users/users-multiple-servers.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -57,17 +57,17 @@ describe('Test users with multiple servers', function () { | |||
57 | password: 'password' | 57 | password: 'password' |
58 | } | 58 | } |
59 | const res = await createUser({ | 59 | const res = await createUser({ |
60 | url: servers[ 0 ].url, | 60 | url: servers[0].url, |
61 | accessToken: servers[ 0 ].accessToken, | 61 | accessToken: servers[0].accessToken, |
62 | username: user.username, | 62 | username: user.username, |
63 | password: user.password | 63 | password: user.password |
64 | }) | 64 | }) |
65 | userId = res.body.user.id | 65 | userId = res.body.user.id |
66 | userAccessToken = await userLogin(servers[ 0 ], user) | 66 | userAccessToken = await userLogin(servers[0], user) |
67 | } | 67 | } |
68 | 68 | ||
69 | { | 69 | { |
70 | const resVideo = await uploadVideo(servers[ 0 ].url, userAccessToken, {}) | 70 | const resVideo = await uploadVideo(servers[0].url, userAccessToken, {}) |
71 | videoUUID = resVideo.body.video.uuid | 71 | videoUUID = resVideo.body.video.uuid |
72 | } | 72 | } |
73 | 73 | ||
@@ -86,7 +86,6 @@ describe('Test users with multiple servers', function () { | |||
86 | const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) | 86 | const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) |
87 | user = res.body | 87 | user = res.body |
88 | 88 | ||
89 | const account: Account = user.account | ||
90 | expect(user.account.displayName).to.equal('my super display name') | 89 | expect(user.account.displayName).to.equal('my super display name') |
91 | 90 | ||
92 | await waitJobs(servers) | 91 | await waitJobs(servers) |
diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts index 7cd61f539..675ebf690 100644 --- a/server/tests/api/users/users-verification.ts +++ b/server/tests/api/users/users-verification.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 24203a731..c0cbce360 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { User, UserRole, Video, MyUser, VideoPlaylistType } from '../../../../shared/index' | 5 | import { MyUser, User, UserRole, Video, VideoAbuseState, VideoAbuseUpdate, VideoPlaylistType } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | addVideoCommentThread, | ||
7 | blockUser, | 8 | blockUser, |
8 | cleanupTests, | 9 | cleanupTests, |
9 | createUser, | 10 | createUser, |
@@ -11,33 +12,41 @@ import { | |||
11 | flushAndRunServer, | 12 | flushAndRunServer, |
12 | getAccountRatings, | 13 | getAccountRatings, |
13 | getBlacklistedVideosList, | 14 | getBlacklistedVideosList, |
15 | getCustomConfig, | ||
14 | getMyUserInformation, | 16 | getMyUserInformation, |
15 | getMyUserVideoQuotaUsed, | 17 | getMyUserVideoQuotaUsed, |
16 | getMyUserVideoRating, | 18 | getMyUserVideoRating, |
17 | getUserInformation, | 19 | getUserInformation, |
18 | getUsersList, | 20 | getUsersList, |
19 | getUsersListPaginationAndSort, | 21 | getUsersListPaginationAndSort, |
22 | getVideoAbusesList, | ||
20 | getVideoChannel, | 23 | getVideoChannel, |
21 | getVideosList, installPlugin, | 24 | getVideosList, |
25 | installPlugin, | ||
22 | login, | 26 | login, |
23 | makePutBodyRequest, | 27 | makePutBodyRequest, |
24 | rateVideo, | 28 | rateVideo, |
25 | registerUserWithChannel, | 29 | registerUserWithChannel, |
26 | removeUser, | 30 | removeUser, |
27 | removeVideo, | 31 | removeVideo, |
32 | reportVideoAbuse, | ||
28 | ServerInfo, | 33 | ServerInfo, |
29 | testImage, | 34 | testImage, |
30 | unblockUser, | 35 | unblockUser, |
36 | updateCustomSubConfig, | ||
31 | updateMyAvatar, | 37 | updateMyAvatar, |
32 | updateMyUser, | 38 | updateMyUser, |
33 | updateUser, | 39 | updateUser, |
40 | updateVideoAbuse, | ||
34 | uploadVideo, | 41 | uploadVideo, |
35 | userLogin | 42 | userLogin, |
43 | waitJobs | ||
36 | } from '../../../../shared/extra-utils' | 44 | } from '../../../../shared/extra-utils' |
37 | import { follow } from '../../../../shared/extra-utils/server/follows' | 45 | import { follow } from '../../../../shared/extra-utils/server/follows' |
38 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' | 46 | import { logout, serverLogin, setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
39 | import { getMyVideos } from '../../../../shared/extra-utils/videos/videos' | 47 | import { getMyVideos } from '../../../../shared/extra-utils/videos/videos' |
40 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | 48 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' |
49 | import { CustomConfig } from '@shared/models/server' | ||
41 | 50 | ||
42 | const expect = chai.expect | 51 | const expect = chai.expect |
43 | 52 | ||
@@ -54,7 +63,14 @@ describe('Test users', function () { | |||
54 | 63 | ||
55 | before(async function () { | 64 | before(async function () { |
56 | this.timeout(30000) | 65 | this.timeout(30000) |
57 | server = await flushAndRunServer(1) | 66 | |
67 | server = await flushAndRunServer(1, { | ||
68 | rates_limit: { | ||
69 | login: { | ||
70 | max: 30 | ||
71 | } | ||
72 | } | ||
73 | }) | ||
58 | 74 | ||
59 | await setAccessTokensToServers([ server ]) | 75 | await setAccessTokensToServers([ server ]) |
60 | 76 | ||
@@ -121,13 +137,13 @@ describe('Test users', function () { | |||
121 | 137 | ||
122 | it('Should be able to login with an insensitive username', async function () { | 138 | it('Should be able to login with an insensitive username', async function () { |
123 | const user = { username: 'RoOt', password: server.user.password } | 139 | const user = { username: 'RoOt', password: server.user.password } |
124 | const res = await login(server.url, server.client, user, 200) | 140 | await login(server.url, server.client, user, 200) |
125 | 141 | ||
126 | const user2 = { username: 'rOoT', password: server.user.password } | 142 | const user2 = { username: 'rOoT', password: server.user.password } |
127 | const res2 = await login(server.url, server.client, user2, 200) | 143 | await login(server.url, server.client, user2, 200) |
128 | 144 | ||
129 | const user3 = { username: 'ROOt', password: server.user.password } | 145 | const user3 = { username: 'ROOt', password: server.user.password } |
130 | const res3 = await login(server.url, server.client, user3, 200) | 146 | await login(server.url, server.client, user3, 200) |
131 | }) | 147 | }) |
132 | }) | 148 | }) |
133 | 149 | ||
@@ -137,7 +153,7 @@ describe('Test users', function () { | |||
137 | const videoAttributes = {} | 153 | const videoAttributes = {} |
138 | await uploadVideo(server.url, accessToken, videoAttributes) | 154 | await uploadVideo(server.url, accessToken, videoAttributes) |
139 | const res = await getVideosList(server.url) | 155 | const res = await getVideosList(server.url) |
140 | const video = res.body.data[ 0 ] | 156 | const video = res.body.data[0] |
141 | 157 | ||
142 | expect(video.account.name).to.equal('root') | 158 | expect(video.account.name).to.equal('root') |
143 | videoId = video.id | 159 | videoId = video.id |
@@ -167,8 +183,8 @@ describe('Test users', function () { | |||
167 | const ratings = res.body | 183 | const ratings = res.body |
168 | 184 | ||
169 | expect(ratings.total).to.equal(1) | 185 | expect(ratings.total).to.equal(1) |
170 | expect(ratings.data[ 0 ].video.id).to.equal(videoId) | 186 | expect(ratings.data[0].video.id).to.equal(videoId) |
171 | expect(ratings.data[ 0 ].rating).to.equal('like') | 187 | expect(ratings.data[0].rating).to.equal('like') |
172 | }) | 188 | }) |
173 | 189 | ||
174 | it('Should retrieve ratings list by rating type', async function () { | 190 | it('Should retrieve ratings list by rating type', async function () { |
@@ -199,13 +215,17 @@ describe('Test users', function () { | |||
199 | }) | 215 | }) |
200 | 216 | ||
201 | describe('Logout', function () { | 217 | describe('Logout', function () { |
202 | it('Should logout (revoke token)') | 218 | it('Should logout (revoke token)', async function () { |
203 | 219 | await logout(server.url, server.accessToken) | |
204 | it('Should not be able to get the user information') | 220 | }) |
205 | 221 | ||
206 | it('Should not be able to upload a video') | 222 | it('Should not be able to get the user information', async function () { |
223 | await getMyUserInformation(server.url, server.accessToken, 401) | ||
224 | }) | ||
207 | 225 | ||
208 | it('Should not be able to remove a video') | 226 | it('Should not be able to upload a video', async function () { |
227 | await uploadVideo(server.url, server.accessToken, { name: 'video' }, 401) | ||
228 | }) | ||
209 | 229 | ||
210 | it('Should not be able to rate a video', async function () { | 230 | it('Should not be able to rate a video', async function () { |
211 | const path = '/api/v1/videos/' | 231 | const path = '/api/v1/videos/' |
@@ -223,13 +243,17 @@ describe('Test users', function () { | |||
223 | await makePutBodyRequest(options) | 243 | await makePutBodyRequest(options) |
224 | }) | 244 | }) |
225 | 245 | ||
226 | it('Should be able to login again') | 246 | it('Should be able to login again', async function () { |
247 | server.accessToken = await serverLogin(server) | ||
248 | }) | ||
227 | 249 | ||
228 | it('Should have an expired access token') | 250 | it('Should have an expired access token') |
229 | 251 | ||
230 | it('Should refresh the token') | 252 | it('Should refresh the token') |
231 | 253 | ||
232 | it('Should be able to upload a video again') | 254 | it('Should be able to get my user information again', async function () { |
255 | await getMyUserInformation(server.url, server.accessToken) | ||
256 | }) | ||
233 | }) | 257 | }) |
234 | 258 | ||
235 | describe('Creating a user', function () { | 259 | describe('Creating a user', function () { |
@@ -253,7 +277,7 @@ describe('Test users', function () { | |||
253 | const res1 = await getMyUserInformation(server.url, accessTokenUser) | 277 | const res1 = await getMyUserInformation(server.url, accessTokenUser) |
254 | const userMe: MyUser = res1.body | 278 | const userMe: MyUser = res1.body |
255 | 279 | ||
256 | const res2 = await getUserInformation(server.url, server.accessToken, userMe.id) | 280 | const res2 = await getUserInformation(server.url, server.accessToken, userMe.id, true) |
257 | const userGet: User = res2.body | 281 | const userGet: User = res2.body |
258 | 282 | ||
259 | for (const user of [ userMe, userGet ]) { | 283 | for (const user of [ userMe, userGet ]) { |
@@ -272,13 +296,23 @@ describe('Test users', function () { | |||
272 | 296 | ||
273 | expect(userMe.specialPlaylists).to.have.lengthOf(1) | 297 | expect(userMe.specialPlaylists).to.have.lengthOf(1) |
274 | expect(userMe.specialPlaylists[0].type).to.equal(VideoPlaylistType.WATCH_LATER) | 298 | expect(userMe.specialPlaylists[0].type).to.equal(VideoPlaylistType.WATCH_LATER) |
299 | |||
300 | // Check stats are included with withStats | ||
301 | expect(userGet.videosCount).to.be.a('number') | ||
302 | expect(userGet.videosCount).to.equal(0) | ||
303 | expect(userGet.videoCommentsCount).to.be.a('number') | ||
304 | expect(userGet.videoCommentsCount).to.equal(0) | ||
305 | expect(userGet.videoAbusesCount).to.be.a('number') | ||
306 | expect(userGet.videoAbusesCount).to.equal(0) | ||
307 | expect(userGet.videoAbusesAcceptedCount).to.be.a('number') | ||
308 | expect(userGet.videoAbusesAcceptedCount).to.equal(0) | ||
275 | }) | 309 | }) |
276 | }) | 310 | }) |
277 | 311 | ||
278 | describe('My videos & quotas', function () { | 312 | describe('My videos & quotas', function () { |
279 | 313 | ||
280 | it('Should be able to upload a video with this user', async function () { | 314 | it('Should be able to upload a video with this user', async function () { |
281 | this.timeout(5000) | 315 | this.timeout(10000) |
282 | 316 | ||
283 | const videoAttributes = { | 317 | const videoAttributes = { |
284 | name: 'super user video', | 318 | name: 'super user video', |
@@ -307,7 +341,7 @@ describe('Test users', function () { | |||
307 | const videos = res.body.data | 341 | const videos = res.body.data |
308 | expect(videos).to.have.lengthOf(1) | 342 | expect(videos).to.have.lengthOf(1) |
309 | 343 | ||
310 | const video: Video = videos[ 0 ] | 344 | const video: Video = videos[0] |
311 | expect(video.name).to.equal('super user video') | 345 | expect(video.name).to.equal('super user video') |
312 | expect(video.thumbnailPath).to.not.be.null | 346 | expect(video.thumbnailPath).to.not.be.null |
313 | expect(video.previewPath).to.not.be.null | 347 | expect(video.previewPath).to.not.be.null |
@@ -330,6 +364,36 @@ describe('Test users', function () { | |||
330 | expect(videos).to.have.lengthOf(0) | 364 | expect(videos).to.have.lengthOf(0) |
331 | } | 365 | } |
332 | }) | 366 | }) |
367 | |||
368 | it('Should disable webtorrent, enable HLS, and update my quota', async function () { | ||
369 | this.timeout(60000) | ||
370 | |||
371 | { | ||
372 | const res = await getCustomConfig(server.url, server.accessToken) | ||
373 | const config = res.body as CustomConfig | ||
374 | config.transcoding.webtorrent.enabled = false | ||
375 | config.transcoding.hls.enabled = true | ||
376 | config.transcoding.enabled = true | ||
377 | await updateCustomSubConfig(server.url, server.accessToken, config) | ||
378 | } | ||
379 | |||
380 | { | ||
381 | const videoAttributes = { | ||
382 | name: 'super user video 2', | ||
383 | fixture: 'video_short.webm' | ||
384 | } | ||
385 | await uploadVideo(server.url, accessTokenUser, videoAttributes) | ||
386 | |||
387 | await waitJobs([ server ]) | ||
388 | } | ||
389 | |||
390 | { | ||
391 | const res = await getMyUserVideoQuotaUsed(server.url, accessTokenUser) | ||
392 | const data = res.body | ||
393 | |||
394 | expect(data.videoQuotaUsed).to.be.greaterThan(220000) | ||
395 | } | ||
396 | }) | ||
333 | }) | 397 | }) |
334 | 398 | ||
335 | describe('Users listing', function () { | 399 | describe('Users listing', function () { |
@@ -344,16 +408,19 @@ describe('Test users', function () { | |||
344 | expect(users).to.be.an('array') | 408 | expect(users).to.be.an('array') |
345 | expect(users.length).to.equal(2) | 409 | expect(users.length).to.equal(2) |
346 | 410 | ||
347 | const user = users[ 0 ] | 411 | const user = users[0] |
348 | expect(user.username).to.equal('user_1') | 412 | expect(user.username).to.equal('user_1') |
349 | expect(user.email).to.equal('user_1@example.com') | 413 | expect(user.email).to.equal('user_1@example.com') |
350 | expect(user.nsfwPolicy).to.equal('display') | 414 | expect(user.nsfwPolicy).to.equal('display') |
351 | 415 | ||
352 | const rootUser = users[ 1 ] | 416 | const rootUser = users[1] |
353 | expect(rootUser.username).to.equal('root') | 417 | expect(rootUser.username).to.equal('root') |
354 | expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') | 418 | expect(rootUser.email).to.equal('admin' + server.internalServerNumber + '@example.com') |
355 | expect(user.nsfwPolicy).to.equal('display') | 419 | expect(user.nsfwPolicy).to.equal('display') |
356 | 420 | ||
421 | expect(rootUser.lastLoginDate).to.exist | ||
422 | expect(user.lastLoginDate).to.exist | ||
423 | |||
357 | userId = user.id | 424 | userId = user.id |
358 | }) | 425 | }) |
359 | 426 | ||
@@ -367,7 +434,7 @@ describe('Test users', function () { | |||
367 | expect(total).to.equal(2) | 434 | expect(total).to.equal(2) |
368 | expect(users.length).to.equal(1) | 435 | expect(users.length).to.equal(1) |
369 | 436 | ||
370 | const user = users[ 0 ] | 437 | const user = users[0] |
371 | expect(user.username).to.equal('root') | 438 | expect(user.username).to.equal('root') |
372 | expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com') | 439 | expect(user.email).to.equal('admin' + server.internalServerNumber + '@example.com') |
373 | expect(user.roleLabel).to.equal('Administrator') | 440 | expect(user.roleLabel).to.equal('Administrator') |
@@ -383,7 +450,7 @@ describe('Test users', function () { | |||
383 | expect(total).to.equal(2) | 450 | expect(total).to.equal(2) |
384 | expect(users.length).to.equal(1) | 451 | expect(users.length).to.equal(1) |
385 | 452 | ||
386 | const user = users[ 0 ] | 453 | const user = users[0] |
387 | expect(user.username).to.equal('user_1') | 454 | expect(user.username).to.equal('user_1') |
388 | expect(user.email).to.equal('user_1@example.com') | 455 | expect(user.email).to.equal('user_1@example.com') |
389 | expect(user.nsfwPolicy).to.equal('display') | 456 | expect(user.nsfwPolicy).to.equal('display') |
@@ -398,7 +465,7 @@ describe('Test users', function () { | |||
398 | expect(total).to.equal(2) | 465 | expect(total).to.equal(2) |
399 | expect(users.length).to.equal(1) | 466 | expect(users.length).to.equal(1) |
400 | 467 | ||
401 | const user = users[ 0 ] | 468 | const user = users[0] |
402 | expect(user.username).to.equal('user_1') | 469 | expect(user.username).to.equal('user_1') |
403 | expect(user.email).to.equal('user_1@example.com') | 470 | expect(user.email).to.equal('user_1@example.com') |
404 | expect(user.nsfwPolicy).to.equal('display') | 471 | expect(user.nsfwPolicy).to.equal('display') |
@@ -413,13 +480,13 @@ describe('Test users', function () { | |||
413 | expect(total).to.equal(2) | 480 | expect(total).to.equal(2) |
414 | expect(users.length).to.equal(2) | 481 | expect(users.length).to.equal(2) |
415 | 482 | ||
416 | expect(users[ 0 ].username).to.equal('root') | 483 | expect(users[0].username).to.equal('root') |
417 | expect(users[ 0 ].email).to.equal('admin' + server.internalServerNumber + '@example.com') | 484 | expect(users[0].email).to.equal('admin' + server.internalServerNumber + '@example.com') |
418 | expect(users[ 0 ].nsfwPolicy).to.equal('display') | 485 | expect(users[0].nsfwPolicy).to.equal('display') |
419 | 486 | ||
420 | expect(users[ 1 ].username).to.equal('user_1') | 487 | expect(users[1].username).to.equal('user_1') |
421 | expect(users[ 1 ].email).to.equal('user_1@example.com') | 488 | expect(users[1].email).to.equal('user_1@example.com') |
422 | expect(users[ 1 ].nsfwPolicy).to.equal('display') | 489 | expect(users[1].nsfwPolicy).to.equal('display') |
423 | }) | 490 | }) |
424 | 491 | ||
425 | it('Should search user by username', async function () { | 492 | it('Should search user by username', async function () { |
@@ -429,7 +496,7 @@ describe('Test users', function () { | |||
429 | expect(res.body.total).to.equal(1) | 496 | expect(res.body.total).to.equal(1) |
430 | expect(users.length).to.equal(1) | 497 | expect(users.length).to.equal(1) |
431 | 498 | ||
432 | expect(users[ 0 ].username).to.equal('root') | 499 | expect(users[0].username).to.equal('root') |
433 | }) | 500 | }) |
434 | 501 | ||
435 | it('Should search user by email', async function () { | 502 | it('Should search user by email', async function () { |
@@ -440,8 +507,8 @@ describe('Test users', function () { | |||
440 | expect(res.body.total).to.equal(1) | 507 | expect(res.body.total).to.equal(1) |
441 | expect(users.length).to.equal(1) | 508 | expect(users.length).to.equal(1) |
442 | 509 | ||
443 | expect(users[ 0 ].username).to.equal('user_1') | 510 | expect(users[0].username).to.equal('user_1') |
444 | expect(users[ 0 ].email).to.equal('user_1@example.com') | 511 | expect(users[0].email).to.equal('user_1@example.com') |
445 | } | 512 | } |
446 | 513 | ||
447 | { | 514 | { |
@@ -451,8 +518,8 @@ describe('Test users', function () { | |||
451 | expect(res.body.total).to.equal(2) | 518 | expect(res.body.total).to.equal(2) |
452 | expect(users.length).to.equal(2) | 519 | expect(users.length).to.equal(2) |
453 | 520 | ||
454 | expect(users[ 0 ].username).to.equal('root') | 521 | expect(users[0].username).to.equal('root') |
455 | expect(users[ 1 ].username).to.equal('user_1') | 522 | expect(users[1].username).to.equal('user_1') |
456 | } | 523 | } |
457 | }) | 524 | }) |
458 | }) | 525 | }) |
@@ -622,7 +689,6 @@ describe('Test users', function () { | |||
622 | }) | 689 | }) |
623 | 690 | ||
624 | describe('Updating another user', function () { | 691 | describe('Updating another user', function () { |
625 | |||
626 | it('Should be able to update another user', async function () { | 692 | it('Should be able to update another user', async function () { |
627 | await updateUser({ | 693 | await updateUser({ |
628 | url: server.url, | 694 | url: server.url, |
@@ -691,12 +757,14 @@ describe('Test users', function () { | |||
691 | 757 | ||
692 | expect(res.body.total).to.equal(1) | 758 | expect(res.body.total).to.equal(1) |
693 | 759 | ||
694 | const video = res.body.data[ 0 ] | 760 | const video = res.body.data[0] |
695 | expect(video.account.name).to.equal('root') | 761 | expect(video.account.name).to.equal('root') |
696 | }) | 762 | }) |
697 | }) | 763 | }) |
698 | 764 | ||
699 | describe('Registering a new user', function () { | 765 | describe('Registering a new user', function () { |
766 | let user15AccessToken | ||
767 | |||
700 | it('Should register a new user', async function () { | 768 | it('Should register a new user', async function () { |
701 | const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' } | 769 | const user = { displayName: 'super user 15', username: 'user_15', password: 'my super password' } |
702 | const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' } | 770 | const channel = { name: 'my_user_15_channel', displayName: 'my channel rocks' } |
@@ -710,18 +778,18 @@ describe('Test users', function () { | |||
710 | password: 'my super password' | 778 | password: 'my super password' |
711 | } | 779 | } |
712 | 780 | ||
713 | accessToken = await userLogin(server, user15) | 781 | user15AccessToken = await userLogin(server, user15) |
714 | }) | 782 | }) |
715 | 783 | ||
716 | it('Should have the correct display name', async function () { | 784 | it('Should have the correct display name', async function () { |
717 | const res = await getMyUserInformation(server.url, accessToken) | 785 | const res = await getMyUserInformation(server.url, user15AccessToken) |
718 | const user: User = res.body | 786 | const user: User = res.body |
719 | 787 | ||
720 | expect(user.account.displayName).to.equal('super user 15') | 788 | expect(user.account.displayName).to.equal('super user 15') |
721 | }) | 789 | }) |
722 | 790 | ||
723 | it('Should have the correct video quota', async function () { | 791 | it('Should have the correct video quota', async function () { |
724 | const res = await getMyUserInformation(server.url, accessToken) | 792 | const res = await getMyUserInformation(server.url, user15AccessToken) |
725 | const user = res.body | 793 | const user = res.body |
726 | 794 | ||
727 | expect(user.videoQuota).to.equal(5 * 1024 * 1024) | 795 | expect(user.videoQuota).to.equal(5 * 1024 * 1024) |
@@ -739,7 +807,7 @@ describe('Test users', function () { | |||
739 | expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined | 807 | expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined |
740 | } | 808 | } |
741 | 809 | ||
742 | await deleteMe(server.url, accessToken) | 810 | await deleteMe(server.url, user15AccessToken) |
743 | 811 | ||
744 | { | 812 | { |
745 | const res = await getUsersList(server.url, server.accessToken) | 813 | const res = await getUsersList(server.url, server.accessToken) |
@@ -749,6 +817,9 @@ describe('Test users', function () { | |||
749 | }) | 817 | }) |
750 | 818 | ||
751 | describe('User blocking', function () { | 819 | describe('User blocking', function () { |
820 | let user16Id | ||
821 | let user16AccessToken | ||
822 | |||
752 | it('Should block and unblock a user', async function () { | 823 | it('Should block and unblock a user', async function () { |
753 | const user16 = { | 824 | const user16 = { |
754 | username: 'user_16', | 825 | username: 'user_16', |
@@ -760,19 +831,95 @@ describe('Test users', function () { | |||
760 | username: user16.username, | 831 | username: user16.username, |
761 | password: user16.password | 832 | password: user16.password |
762 | }) | 833 | }) |
763 | const user16Id = resUser.body.user.id | 834 | user16Id = resUser.body.user.id |
764 | 835 | ||
765 | accessToken = await userLogin(server, user16) | 836 | user16AccessToken = await userLogin(server, user16) |
766 | 837 | ||
767 | await getMyUserInformation(server.url, accessToken, 200) | 838 | await getMyUserInformation(server.url, user16AccessToken, 200) |
768 | await blockUser(server.url, user16Id, server.accessToken) | 839 | await blockUser(server.url, user16Id, server.accessToken) |
769 | 840 | ||
770 | await getMyUserInformation(server.url, accessToken, 401) | 841 | await getMyUserInformation(server.url, user16AccessToken, 401) |
771 | await userLogin(server, user16, 400) | 842 | await userLogin(server, user16, 400) |
772 | 843 | ||
773 | await unblockUser(server.url, user16Id, server.accessToken) | 844 | await unblockUser(server.url, user16Id, server.accessToken) |
774 | accessToken = await userLogin(server, user16) | 845 | user16AccessToken = await userLogin(server, user16) |
775 | await getMyUserInformation(server.url, accessToken, 200) | 846 | await getMyUserInformation(server.url, user16AccessToken, 200) |
847 | }) | ||
848 | }) | ||
849 | |||
850 | describe('User stats', function () { | ||
851 | let user17Id | ||
852 | let user17AccessToken | ||
853 | |||
854 | it('Should report correct initial statistics about a user', async function () { | ||
855 | const user17 = { | ||
856 | username: 'user_17', | ||
857 | password: 'my super password' | ||
858 | } | ||
859 | const resUser = await createUser({ | ||
860 | url: server.url, | ||
861 | accessToken: server.accessToken, | ||
862 | username: user17.username, | ||
863 | password: user17.password | ||
864 | }) | ||
865 | |||
866 | user17Id = resUser.body.user.id | ||
867 | user17AccessToken = await userLogin(server, user17) | ||
868 | |||
869 | const res = await getUserInformation(server.url, server.accessToken, user17Id, true) | ||
870 | const user: User = res.body | ||
871 | |||
872 | expect(user.videosCount).to.equal(0) | ||
873 | expect(user.videoCommentsCount).to.equal(0) | ||
874 | expect(user.videoAbusesCount).to.equal(0) | ||
875 | expect(user.videoAbusesCreatedCount).to.equal(0) | ||
876 | expect(user.videoAbusesAcceptedCount).to.equal(0) | ||
877 | }) | ||
878 | |||
879 | it('Should report correct videos count', async function () { | ||
880 | const videoAttributes = { | ||
881 | name: 'video to test user stats' | ||
882 | } | ||
883 | await uploadVideo(server.url, user17AccessToken, videoAttributes) | ||
884 | const res1 = await getVideosList(server.url) | ||
885 | videoId = res1.body.data.find(video => video.name === videoAttributes.name).id | ||
886 | |||
887 | const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true) | ||
888 | const user: User = res2.body | ||
889 | |||
890 | expect(user.videosCount).to.equal(1) | ||
891 | }) | ||
892 | |||
893 | it('Should report correct video comments for user', async function () { | ||
894 | const text = 'super comment' | ||
895 | await addVideoCommentThread(server.url, user17AccessToken, videoId, text) | ||
896 | |||
897 | const res = await getUserInformation(server.url, server.accessToken, user17Id, true) | ||
898 | const user: User = res.body | ||
899 | |||
900 | expect(user.videoCommentsCount).to.equal(1) | ||
901 | }) | ||
902 | |||
903 | it('Should report correct video abuses counts', async function () { | ||
904 | const reason = 'my super bad reason' | ||
905 | await reportVideoAbuse(server.url, user17AccessToken, videoId, reason) | ||
906 | |||
907 | const res1 = await getVideoAbusesList({ url: server.url, token: server.accessToken }) | ||
908 | const abuseId = res1.body.data[0].id | ||
909 | |||
910 | const res2 = await getUserInformation(server.url, server.accessToken, user17Id, true) | ||
911 | const user2: User = res2.body | ||
912 | |||
913 | expect(user2.videoAbusesCount).to.equal(1) // number of incriminations | ||
914 | expect(user2.videoAbusesCreatedCount).to.equal(1) // number of reports created | ||
915 | |||
916 | const body: VideoAbuseUpdate = { state: VideoAbuseState.ACCEPTED } | ||
917 | await updateVideoAbuse(server.url, server.accessToken, videoId, abuseId, body) | ||
918 | |||
919 | const res3 = await getUserInformation(server.url, server.accessToken, user17Id, true) | ||
920 | const user3: User = res3.body | ||
921 | |||
922 | expect(user3.videoAbusesAcceptedCount).to.equal(1) // number of reports created accepted | ||
776 | }) | 923 | }) |
777 | }) | 924 | }) |
778 | 925 | ||
diff --git a/server/tests/api/videos/audio-only.ts b/server/tests/api/videos/audio-only.ts index f12d730cc..ac7a0b89c 100644 --- a/server/tests/api/videos/audio-only.ts +++ b/server/tests/api/videos/audio-only.ts | |||
@@ -1,28 +1,21 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | checkDirectoryIsEmpty, | ||
7 | checkSegmentHash, | ||
8 | checkTmpIsEmpty, | ||
9 | cleanupTests, | 6 | cleanupTests, |
10 | doubleFollow, | 7 | doubleFollow, |
11 | flushAndRunMultipleServers, | 8 | flushAndRunMultipleServers, |
12 | getPlaylist, | 9 | getVideo, |
13 | getVideo, makeGetRequest, makeRawRequest, | 10 | root, |
14 | removeVideo, root, | ||
15 | ServerInfo, | 11 | ServerInfo, |
16 | setAccessTokensToServers, updateCustomSubConfig, | 12 | setAccessTokensToServers, |
17 | updateVideo, | ||
18 | uploadVideo, | 13 | uploadVideo, |
19 | waitJobs, webtorrentAdd | 14 | waitJobs |
20 | } from '../../../../shared/extra-utils' | 15 | } from '../../../../shared/extra-utils' |
21 | import { VideoDetails } from '../../../../shared/models/videos' | 16 | import { VideoDetails } from '../../../../shared/models/videos' |
22 | import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' | ||
23 | import { join } from 'path' | 17 | import { join } from 'path' |
24 | import { DEFAULT_AUDIO_RESOLUTION } from '../../../initializers/constants' | 18 | import { audio, getVideoStreamSize } from '@server/helpers/ffmpeg-utils' |
25 | import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution, audio, getVideoStreamSize } from '@server/helpers/ffmpeg-utils' | ||
26 | 19 | ||
27 | const expect = chai.expect | 20 | const expect = chai.expect |
28 | 21 | ||
@@ -87,14 +80,14 @@ describe('Test audio only video transcoding', function () { | |||
87 | 80 | ||
88 | it('0p transcoded video should not have video', async function () { | 81 | it('0p transcoded video should not have video', async function () { |
89 | const paths = [ | 82 | const paths = [ |
90 | join(root(), 'test' + servers[ 0 ].internalServerNumber, 'videos', videoUUID + '-0.mp4'), | 83 | join(root(), 'test' + servers[0].internalServerNumber, 'videos', videoUUID + '-0.mp4'), |
91 | join(root(), 'test' + servers[ 0 ].internalServerNumber, 'streaming-playlists', 'hls', videoUUID, videoUUID + '-0-fragmented.mp4') | 84 | join(root(), 'test' + servers[0].internalServerNumber, 'streaming-playlists', 'hls', videoUUID, videoUUID + '-0-fragmented.mp4') |
92 | ] | 85 | ] |
93 | 86 | ||
94 | for (const path of paths) { | 87 | for (const path of paths) { |
95 | const { audioStream } = await audio.get(path) | 88 | const { audioStream } = await audio.get(path) |
96 | expect(audioStream[ 'codec_name' ]).to.be.equal('aac') | 89 | expect(audioStream['codec_name']).to.be.equal('aac') |
97 | expect(audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000) | 90 | expect(audioStream['bit_rate']).to.be.at.most(384 * 8000) |
98 | 91 | ||
99 | const size = await getVideoStreamSize(path) | 92 | const size = await getVideoStreamSize(path) |
100 | expect(size.height).to.equal(0) | 93 | expect(size.height).to.equal(0) |
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index fa3e250ec..e3029f1ae 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -63,9 +63,9 @@ describe('Test multiple servers', function () { | |||
63 | displayName: 'my channel', | 63 | displayName: 'my channel', |
64 | description: 'super channel' | 64 | description: 'super channel' |
65 | } | 65 | } |
66 | await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) | 66 | await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel) |
67 | const channelRes = await getVideoChannelsList(servers[ 0 ].url, 0, 1) | 67 | const channelRes = await getVideoChannelsList(servers[0].url, 0, 1) |
68 | videoChannelId = channelRes.body.data[ 0 ].id | 68 | videoChannelId = channelRes.body.data[0].id |
69 | } | 69 | } |
70 | 70 | ||
71 | // Server 1 and server 2 follow each other | 71 | // Server 1 and server 2 follow each other |
@@ -163,7 +163,7 @@ describe('Test multiple servers', function () { | |||
163 | username: 'user1', | 163 | username: 'user1', |
164 | password: 'super_password' | 164 | password: 'super_password' |
165 | } | 165 | } |
166 | await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) | 166 | await createUser({ url: servers[1].url, accessToken: servers[1].accessToken, username: user.username, password: user.password }) |
167 | const userAccessToken = await userLogin(servers[1], user) | 167 | const userAccessToken = await userLogin(servers[1], user) |
168 | 168 | ||
169 | const videoAttributes = { | 169 | const videoAttributes = { |
@@ -762,12 +762,12 @@ describe('Test multiple servers', function () { | |||
762 | 762 | ||
763 | { | 763 | { |
764 | const text = 'my super first comment' | 764 | const text = 'my super first comment' |
765 | await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, text) | 765 | await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoUUID, text) |
766 | } | 766 | } |
767 | 767 | ||
768 | { | 768 | { |
769 | const text = 'my super second comment' | 769 | const text = 'my super second comment' |
770 | await addVideoCommentThread(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, text) | 770 | await addVideoCommentThread(servers[2].url, servers[2].accessToken, videoUUID, text) |
771 | } | 771 | } |
772 | 772 | ||
773 | await waitJobs(servers) | 773 | await waitJobs(servers) |
@@ -777,7 +777,7 @@ describe('Test multiple servers', function () { | |||
777 | const threadId = res.body.data.find(c => c.text === 'my super first comment').id | 777 | const threadId = res.body.data.find(c => c.text === 'my super first comment').id |
778 | 778 | ||
779 | const text = 'my super answer to thread 1' | 779 | const text = 'my super answer to thread 1' |
780 | await addVideoCommentReply(servers[ 1 ].url, servers[ 1 ].accessToken, videoUUID, threadId, text) | 780 | await addVideoCommentReply(servers[1].url, servers[1].accessToken, videoUUID, threadId, text) |
781 | } | 781 | } |
782 | 782 | ||
783 | await waitJobs(servers) | 783 | await waitJobs(servers) |
@@ -790,10 +790,10 @@ describe('Test multiple servers', function () { | |||
790 | const childCommentId = res2.body.children[0].comment.id | 790 | const childCommentId = res2.body.children[0].comment.id |
791 | 791 | ||
792 | const text3 = 'my second answer to thread 1' | 792 | const text3 = 'my second answer to thread 1' |
793 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, threadId, text3) | 793 | await addVideoCommentReply(servers[2].url, servers[2].accessToken, videoUUID, threadId, text3) |
794 | 794 | ||
795 | const text2 = 'my super answer to answer of thread 1' | 795 | const text2 = 'my super answer to answer of thread 1' |
796 | await addVideoCommentReply(servers[ 2 ].url, servers[ 2 ].accessToken, videoUUID, childCommentId, text2) | 796 | await addVideoCommentReply(servers[2].url, servers[2].accessToken, videoUUID, childCommentId, text2) |
797 | } | 797 | } |
798 | 798 | ||
799 | await waitJobs(servers) | 799 | await waitJobs(servers) |
@@ -900,9 +900,9 @@ describe('Test multiple servers', function () { | |||
900 | it('Should delete the thread comments', async function () { | 900 | it('Should delete the thread comments', async function () { |
901 | this.timeout(10000) | 901 | this.timeout(10000) |
902 | 902 | ||
903 | const res = await getVideoCommentThreads(servers[ 0 ].url, videoUUID, 0, 5) | 903 | const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5) |
904 | const threadId = res.body.data.find(c => c.text === 'my super first comment').id | 904 | const threadId = res.body.data.find(c => c.text === 'my super first comment').id |
905 | await deleteVideoComment(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, threadId) | 905 | await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId) |
906 | 906 | ||
907 | await waitJobs(servers) | 907 | await waitJobs(servers) |
908 | }) | 908 | }) |
@@ -945,9 +945,9 @@ describe('Test multiple servers', function () { | |||
945 | it('Should delete a remote thread by the origin server', async function () { | 945 | it('Should delete a remote thread by the origin server', async function () { |
946 | this.timeout(5000) | 946 | this.timeout(5000) |
947 | 947 | ||
948 | const res = await getVideoCommentThreads(servers[ 0 ].url, videoUUID, 0, 5) | 948 | const res = await getVideoCommentThreads(servers[0].url, videoUUID, 0, 5) |
949 | const threadId = res.body.data.find(c => c.text === 'my super second comment').id | 949 | const threadId = res.body.data.find(c => c.text === 'my super second comment').id |
950 | await deleteVideoComment(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, threadId) | 950 | await deleteVideoComment(servers[0].url, servers[0].accessToken, videoUUID, threadId) |
951 | 951 | ||
952 | await waitJobs(servers) | 952 | await waitJobs(servers) |
953 | }) | 953 | }) |
@@ -1021,7 +1021,7 @@ describe('Test multiple servers', function () { | |||
1021 | const filePath = join(__dirname, '..', '..', 'fixtures', 'video_short.webm') | 1021 | const filePath = join(__dirname, '..', '..', 'fixtures', 'video_short.webm') |
1022 | 1022 | ||
1023 | await req.attach('videofile', filePath) | 1023 | await req.attach('videofile', filePath) |
1024 | .expect(200) | 1024 | .expect(200) |
1025 | 1025 | ||
1026 | await waitJobs(servers) | 1026 | await waitJobs(servers) |
1027 | 1027 | ||
@@ -1046,7 +1046,7 @@ describe('Test multiple servers', function () { | |||
1046 | duration: 5, | 1046 | duration: 5, |
1047 | commentsEnabled: true, | 1047 | commentsEnabled: true, |
1048 | downloadEnabled: true, | 1048 | downloadEnabled: true, |
1049 | tags: [ ], | 1049 | tags: [], |
1050 | privacy: VideoPrivacy.PUBLIC, | 1050 | privacy: VideoPrivacy.PUBLIC, |
1051 | channel: { | 1051 | channel: { |
1052 | displayName: 'Main root channel', | 1052 | displayName: 'Main root channel', |
diff --git a/server/tests/api/videos/services.ts b/server/tests/api/videos/services.ts index 17172331f..5505a845a 100644 --- a/server/tests/api/videos/services.ts +++ b/server/tests/api/videos/services.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -31,8 +31,8 @@ describe('Test services', function () { | |||
31 | 31 | ||
32 | const res = await getOEmbed(server.url, oembedUrl) | 32 | const res = await getOEmbed(server.url, oembedUrl) |
33 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + | 33 | const expectedHtml = '<iframe width="560" height="315" sandbox="allow-same-origin allow-scripts" ' + |
34 | `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + | 34 | `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + |
35 | 'frameborder="0" allowfullscreen></iframe>' | 35 | 'frameborder="0" allowfullscreen></iframe>' |
36 | const expectedThumbnailUrl = 'http://localhost:' + server.port + '/static/previews/' + server.video.uuid + '.jpg' | 36 | const expectedThumbnailUrl = 'http://localhost:' + server.port + '/static/previews/' + server.video.uuid + '.jpg' |
37 | 37 | ||
38 | expect(res.body.html).to.equal(expectedHtml) | 38 | expect(res.body.html).to.equal(expectedHtml) |
@@ -53,8 +53,8 @@ describe('Test services', function () { | |||
53 | 53 | ||
54 | const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) | 54 | const res = await getOEmbed(server.url, oembedUrl, format, maxHeight, maxWidth) |
55 | const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + | 55 | const expectedHtml = '<iframe width="50" height="50" sandbox="allow-same-origin allow-scripts" ' + |
56 | `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + | 56 | `src="http://localhost:${server.port}/videos/embed/${server.video.uuid}" ` + |
57 | 'frameborder="0" allowfullscreen></iframe>' | 57 | 'frameborder="0" allowfullscreen></iframe>' |
58 | 58 | ||
59 | expect(res.body.html).to.equal(expectedHtml) | 59 | expect(res.body.html).to.equal(expectedHtml) |
60 | expect(res.body.title).to.equal(server.video.name) | 60 | expect(res.body.title).to.equal(server.video.name) |
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index 362d6b78f..0ae405950 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import { keyBy } from 'lodash' | 4 | import { keyBy } from 'lodash' |
@@ -34,6 +34,7 @@ const expect = chai.expect | |||
34 | describe('Test a single server', function () { | 34 | describe('Test a single server', function () { |
35 | let server: ServerInfo = null | 35 | let server: ServerInfo = null |
36 | let videoId = -1 | 36 | let videoId = -1 |
37 | let videoId2 = -1 | ||
37 | let videoUUID = '' | 38 | let videoUUID = '' |
38 | let videosListBase: any[] = null | 39 | let videosListBase: any[] = null |
39 | 40 | ||
@@ -237,12 +238,11 @@ describe('Test a single server', function () { | |||
237 | it('Should upload 6 videos', async function () { | 238 | it('Should upload 6 videos', async function () { |
238 | this.timeout(25000) | 239 | this.timeout(25000) |
239 | 240 | ||
240 | const videos = [ | 241 | const videos = new Set([ |
241 | 'video_short.mp4', 'video_short.ogv', 'video_short.webm', | 242 | 'video_short.mp4', 'video_short.ogv', 'video_short.webm', |
242 | 'video_short1.webm', 'video_short2.webm', 'video_short3.webm' | 243 | 'video_short1.webm', 'video_short2.webm', 'video_short3.webm' |
243 | ] | 244 | ]) |
244 | 245 | ||
245 | const tasks: Promise<any>[] = [] | ||
246 | for (const video of videos) { | 246 | for (const video of videos) { |
247 | const videoAttributes = { | 247 | const videoAttributes = { |
248 | name: video + ' name', | 248 | name: video + ' name', |
@@ -255,11 +255,8 @@ describe('Test a single server', function () { | |||
255 | fixture: video | 255 | fixture: video |
256 | } | 256 | } |
257 | 257 | ||
258 | const p = uploadVideo(server.url, server.accessToken, videoAttributes) | 258 | await uploadVideo(server.url, server.accessToken, videoAttributes) |
259 | tasks.push(p) | ||
260 | } | 259 | } |
261 | |||
262 | await Promise.all(tasks) | ||
263 | }) | 260 | }) |
264 | 261 | ||
265 | it('Should have the correct durations', async function () { | 262 | it('Should have the correct durations', async function () { |
@@ -345,6 +342,7 @@ describe('Test a single server', function () { | |||
345 | expect(videos[5].name).to.equal('video_short1.webm name') | 342 | expect(videos[5].name).to.equal('video_short1.webm name') |
346 | 343 | ||
347 | videoId = videos[3].uuid | 344 | videoId = videos[3].uuid |
345 | videoId2 = videos[5].uuid | ||
348 | }) | 346 | }) |
349 | 347 | ||
350 | it('Should list and sort by trending in descending order', async function () { | 348 | it('Should list and sort by trending in descending order', async function () { |
@@ -433,6 +431,43 @@ describe('Test a single server', function () { | |||
433 | expect(video.dislikes).to.equal(1) | 431 | expect(video.dislikes).to.equal(1) |
434 | }) | 432 | }) |
435 | 433 | ||
434 | it('Should sort by originallyPublishedAt', async function () { | ||
435 | { | ||
436 | |||
437 | { | ||
438 | const now = new Date() | ||
439 | const attributes = { originallyPublishedAt: now.toISOString() } | ||
440 | await updateVideo(server.url, server.accessToken, videoId, attributes) | ||
441 | |||
442 | const res = await getVideosListSort(server.url, '-originallyPublishedAt') | ||
443 | const names = res.body.data.map(v => v.name) | ||
444 | |||
445 | expect(names[0]).to.equal('my super video updated') | ||
446 | expect(names[1]).to.equal('video_short2.webm name') | ||
447 | expect(names[2]).to.equal('video_short1.webm name') | ||
448 | expect(names[3]).to.equal('video_short.webm name') | ||
449 | expect(names[4]).to.equal('video_short.ogv name') | ||
450 | expect(names[5]).to.equal('video_short.mp4 name') | ||
451 | } | ||
452 | |||
453 | { | ||
454 | const now = new Date() | ||
455 | const attributes = { originallyPublishedAt: now.toISOString() } | ||
456 | await updateVideo(server.url, server.accessToken, videoId2, attributes) | ||
457 | |||
458 | const res = await getVideosListSort(server.url, '-originallyPublishedAt') | ||
459 | const names = res.body.data.map(v => v.name) | ||
460 | |||
461 | expect(names[0]).to.equal('video_short1.webm name') | ||
462 | expect(names[1]).to.equal('my super video updated') | ||
463 | expect(names[2]).to.equal('video_short2.webm name') | ||
464 | expect(names[3]).to.equal('video_short.webm name') | ||
465 | expect(names[4]).to.equal('video_short.ogv name') | ||
466 | expect(names[5]).to.equal('video_short.mp4 name') | ||
467 | } | ||
468 | } | ||
469 | }) | ||
470 | |||
436 | after(async function () { | 471 | after(async function () { |
437 | await cleanupTests([ server ]) | 472 | await cleanupTests([ server ]) |
438 | }) | 473 | }) |
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts index 0cd6f22c7..a96be97f6 100644 --- a/server/tests/api/videos/video-abuse.ts +++ b/server/tests/api/videos/video-abuse.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -13,7 +13,10 @@ import { | |||
13 | ServerInfo, | 13 | ServerInfo, |
14 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
15 | updateVideoAbuse, | 15 | updateVideoAbuse, |
16 | uploadVideo | 16 | uploadVideo, |
17 | removeVideo, | ||
18 | createUser, | ||
19 | userLogin | ||
17 | } from '../../../../shared/extra-utils/index' | 20 | } from '../../../../shared/extra-utils/index' |
18 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' | 21 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 22 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
@@ -68,7 +71,7 @@ describe('Test video abuses', function () { | |||
68 | }) | 71 | }) |
69 | 72 | ||
70 | it('Should not have video abuses', async function () { | 73 | it('Should not have video abuses', async function () { |
71 | const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | 74 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
72 | 75 | ||
73 | expect(res.body.total).to.equal(0) | 76 | expect(res.body.total).to.equal(0) |
74 | expect(res.body.data).to.be.an('array') | 77 | expect(res.body.data).to.be.an('array') |
@@ -86,7 +89,7 @@ describe('Test video abuses', function () { | |||
86 | }) | 89 | }) |
87 | 90 | ||
88 | it('Should have 1 video abuses on server 1 and 0 on server 2', async function () { | 91 | it('Should have 1 video abuses on server 1 and 0 on server 2', async function () { |
89 | const res1 = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | 92 | const res1 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
90 | 93 | ||
91 | expect(res1.body.total).to.equal(1) | 94 | expect(res1.body.total).to.equal(1) |
92 | expect(res1.body.data).to.be.an('array') | 95 | expect(res1.body.data).to.be.an('array') |
@@ -97,8 +100,13 @@ describe('Test video abuses', function () { | |||
97 | expect(abuse.reporterAccount.name).to.equal('root') | 100 | expect(abuse.reporterAccount.name).to.equal('root') |
98 | expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) | 101 | expect(abuse.reporterAccount.host).to.equal('localhost:' + servers[0].port) |
99 | expect(abuse.video.id).to.equal(servers[0].video.id) | 102 | expect(abuse.video.id).to.equal(servers[0].video.id) |
103 | expect(abuse.video.channel).to.exist | ||
104 | expect(abuse.count).to.equal(1) | ||
105 | expect(abuse.nth).to.equal(1) | ||
106 | expect(abuse.countReportsForReporter).to.equal(1) | ||
107 | expect(abuse.countReportsForReportee).to.equal(1) | ||
100 | 108 | ||
101 | const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | 109 | const res2 = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
102 | expect(res2.body.total).to.equal(0) | 110 | expect(res2.body.total).to.equal(0) |
103 | expect(res2.body.data).to.be.an('array') | 111 | expect(res2.body.data).to.be.an('array') |
104 | expect(res2.body.data.length).to.equal(0) | 112 | expect(res2.body.data.length).to.equal(0) |
@@ -115,7 +123,7 @@ describe('Test video abuses', function () { | |||
115 | }) | 123 | }) |
116 | 124 | ||
117 | it('Should have 2 video abuses on server 1 and 1 on server 2', async function () { | 125 | it('Should have 2 video abuses on server 1 and 1 on server 2', async function () { |
118 | const res1 = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | 126 | const res1 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
119 | expect(res1.body.total).to.equal(2) | 127 | expect(res1.body.total).to.equal(2) |
120 | expect(res1.body.data).to.be.an('array') | 128 | expect(res1.body.data).to.be.an('array') |
121 | expect(res1.body.data.length).to.equal(2) | 129 | expect(res1.body.data.length).to.equal(2) |
@@ -128,6 +136,8 @@ describe('Test video abuses', function () { | |||
128 | expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING) | 136 | expect(abuse1.state.id).to.equal(VideoAbuseState.PENDING) |
129 | expect(abuse1.state.label).to.equal('Pending') | 137 | expect(abuse1.state.label).to.equal('Pending') |
130 | expect(abuse1.moderationComment).to.be.null | 138 | expect(abuse1.moderationComment).to.be.null |
139 | expect(abuse1.count).to.equal(1) | ||
140 | expect(abuse1.nth).to.equal(1) | ||
131 | 141 | ||
132 | const abuse2: VideoAbuse = res1.body.data[1] | 142 | const abuse2: VideoAbuse = res1.body.data[1] |
133 | expect(abuse2.reason).to.equal('my super bad reason 2') | 143 | expect(abuse2.reason).to.equal('my super bad reason 2') |
@@ -138,7 +148,7 @@ describe('Test video abuses', function () { | |||
138 | expect(abuse2.state.label).to.equal('Pending') | 148 | expect(abuse2.state.label).to.equal('Pending') |
139 | expect(abuse2.moderationComment).to.be.null | 149 | expect(abuse2.moderationComment).to.be.null |
140 | 150 | ||
141 | const res2 = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | 151 | const res2 = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
142 | expect(res2.body.total).to.equal(1) | 152 | expect(res2.body.total).to.equal(1) |
143 | expect(res2.body.data).to.be.an('array') | 153 | expect(res2.body.data).to.be.an('array') |
144 | expect(res2.body.data.length).to.equal(1) | 154 | expect(res2.body.data.length).to.equal(1) |
@@ -156,7 +166,7 @@ describe('Test video abuses', function () { | |||
156 | const body = { state: VideoAbuseState.REJECTED } | 166 | const body = { state: VideoAbuseState.REJECTED } |
157 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) | 167 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) |
158 | 168 | ||
159 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | 169 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
160 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.REJECTED) | 170 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.REJECTED) |
161 | }) | 171 | }) |
162 | 172 | ||
@@ -164,7 +174,7 @@ describe('Test video abuses', function () { | |||
164 | const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' } | 174 | const body = { state: VideoAbuseState.ACCEPTED, moderationComment: 'It is valid' } |
165 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) | 175 | await updateVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id, body) |
166 | 176 | ||
167 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | 177 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
168 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.ACCEPTED) | 178 | expect(res.body.data[0].state.id).to.equal(VideoAbuseState.ACCEPTED) |
169 | expect(res.body.data[0].moderationComment).to.equal('It is valid') | 179 | expect(res.body.data[0].moderationComment).to.equal('It is valid') |
170 | }) | 180 | }) |
@@ -176,16 +186,16 @@ describe('Test video abuses', function () { | |||
176 | await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this') | 186 | await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this') |
177 | await waitJobs(servers) | 187 | await waitJobs(servers) |
178 | 188 | ||
179 | const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | 189 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
180 | expect(res.body.total).to.equal(3) | 190 | expect(res.body.total).to.equal(3) |
181 | } | 191 | } |
182 | 192 | ||
183 | const accountToBlock = 'root@localhost:' + servers[1].port | 193 | const accountToBlock = 'root@localhost:' + servers[1].port |
184 | 194 | ||
185 | { | 195 | { |
186 | await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) | 196 | await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) |
187 | 197 | ||
188 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | 198 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
189 | expect(res.body.total).to.equal(2) | 199 | expect(res.body.total).to.equal(2) |
190 | 200 | ||
191 | const abuse = res.body.data.find(a => a.reason === 'will mute this') | 201 | const abuse = res.body.data.find(a => a.reason === 'will mute this') |
@@ -193,9 +203,9 @@ describe('Test video abuses', function () { | |||
193 | } | 203 | } |
194 | 204 | ||
195 | { | 205 | { |
196 | await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) | 206 | await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, accountToBlock) |
197 | 207 | ||
198 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | 208 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
199 | expect(res.body.total).to.equal(3) | 209 | expect(res.body.total).to.equal(3) |
200 | } | 210 | } |
201 | }) | 211 | }) |
@@ -204,9 +214,9 @@ describe('Test video abuses', function () { | |||
204 | const serverToBlock = servers[1].host | 214 | const serverToBlock = servers[1].host |
205 | 215 | ||
206 | { | 216 | { |
207 | await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, servers[1].host) | 217 | await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, servers[1].host) |
208 | 218 | ||
209 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | 219 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
210 | expect(res.body.total).to.equal(2) | 220 | expect(res.body.total).to.equal(2) |
211 | 221 | ||
212 | const abuse = res.body.data.find(a => a.reason === 'will mute this') | 222 | const abuse = res.body.data.find(a => a.reason === 'will mute this') |
@@ -214,13 +224,73 @@ describe('Test video abuses', function () { | |||
214 | } | 224 | } |
215 | 225 | ||
216 | { | 226 | { |
217 | await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, serverToBlock) | 227 | await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, serverToBlock) |
218 | 228 | ||
219 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | 229 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
220 | expect(res.body.total).to.equal(3) | 230 | expect(res.body.total).to.equal(3) |
221 | } | 231 | } |
222 | }) | 232 | }) |
223 | 233 | ||
234 | it('Should keep the video abuse when deleting the video', async function () { | ||
235 | this.timeout(10000) | ||
236 | |||
237 | await removeVideo(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid) | ||
238 | |||
239 | await waitJobs(servers) | ||
240 | |||
241 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) | ||
242 | expect(res.body.total).to.equal(2, "wrong number of videos returned") | ||
243 | expect(res.body.data.length).to.equal(2, "wrong number of videos returned") | ||
244 | expect(res.body.data[0].id).to.equal(abuseServer2.id, "wrong origin server id for first video") | ||
245 | |||
246 | const abuse: VideoAbuse = res.body.data[0] | ||
247 | expect(abuse.video.id).to.equal(abuseServer2.video.id, "wrong video id") | ||
248 | expect(abuse.video.channel).to.exist | ||
249 | expect(abuse.video.deleted).to.be.true | ||
250 | }) | ||
251 | |||
252 | it('Should include counts of reports from reporter and reportee', async function () { | ||
253 | this.timeout(10000) | ||
254 | |||
255 | // register a second user to have two reporters/reportees | ||
256 | const user = { username: 'user2', password: 'password' } | ||
257 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, ...user }) | ||
258 | const userAccessToken = await userLogin(servers[0], user) | ||
259 | |||
260 | // upload a third video via this user | ||
261 | const video3Attributes = { | ||
262 | name: 'my second super name for server 1', | ||
263 | description: 'my second super description for server 1' | ||
264 | } | ||
265 | await uploadVideo(servers[0].url, userAccessToken, video3Attributes) | ||
266 | |||
267 | const res1 = await getVideosList(servers[0].url) | ||
268 | const videos = res1.body.data | ||
269 | const video3 = videos.find(video => video.name === 'my second super name for server 1') | ||
270 | |||
271 | // resume with the test | ||
272 | const reason3 = 'my super bad reason 3' | ||
273 | await reportVideoAbuse(servers[0].url, servers[0].accessToken, video3.id, reason3) | ||
274 | const reason4 = 'my super bad reason 4' | ||
275 | await reportVideoAbuse(servers[0].url, userAccessToken, servers[0].video.id, reason4) | ||
276 | |||
277 | const res2 = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) | ||
278 | |||
279 | { | ||
280 | for (const abuse of res2.body.data as VideoAbuse[]) { | ||
281 | if (abuse.video.id === video3.id) { | ||
282 | expect(abuse.count).to.equal(1, "wrong reports count for video 3") | ||
283 | expect(abuse.nth).to.equal(1, "wrong report position in report list for video 3") | ||
284 | expect(abuse.countReportsForReportee).to.equal(1, "wrong reports count for reporter on video 3 abuse") | ||
285 | expect(abuse.countReportsForReporter).to.equal(3, "wrong reports count for reportee on video 3 abuse") | ||
286 | } | ||
287 | if (abuse.video.id === servers[0].video.id) { | ||
288 | expect(abuse.countReportsForReportee).to.equal(3, "wrong reports count for reporter on video 1 abuse") | ||
289 | } | ||
290 | } | ||
291 | } | ||
292 | }) | ||
293 | |||
224 | it('Should delete the video abuse', async function () { | 294 | it('Should delete the video abuse', async function () { |
225 | this.timeout(10000) | 295 | this.timeout(10000) |
226 | 296 | ||
@@ -229,16 +299,54 @@ describe('Test video abuses', function () { | |||
229 | await waitJobs(servers) | 299 | await waitJobs(servers) |
230 | 300 | ||
231 | { | 301 | { |
232 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | 302 | const res = await getVideoAbusesList({ url: servers[1].url, token: servers[1].accessToken }) |
233 | expect(res.body.total).to.equal(1) | 303 | expect(res.body.total).to.equal(1) |
234 | expect(res.body.data.length).to.equal(1) | 304 | expect(res.body.data.length).to.equal(1) |
235 | expect(res.body.data[0].id).to.not.equal(abuseServer2.id) | 305 | expect(res.body.data[0].id).to.not.equal(abuseServer2.id) |
236 | } | 306 | } |
237 | 307 | ||
238 | { | 308 | { |
239 | const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | 309 | const res = await getVideoAbusesList({ url: servers[0].url, token: servers[0].accessToken }) |
240 | expect(res.body.total).to.equal(3) | 310 | expect(res.body.total).to.equal(5) |
311 | } | ||
312 | }) | ||
313 | |||
314 | it('Should list and filter video abuses', async function () { | ||
315 | async function list (query: Omit<Parameters<typeof getVideoAbusesList>[0], 'url' | 'token'>) { | ||
316 | const options = { | ||
317 | url: servers[0].url, | ||
318 | token: servers[0].accessToken | ||
319 | } | ||
320 | |||
321 | Object.assign(options, query) | ||
322 | |||
323 | const res = await getVideoAbusesList(options) | ||
324 | |||
325 | return res.body.data as VideoAbuse[] | ||
241 | } | 326 | } |
327 | |||
328 | expect(await list({ id: 56 })).to.have.lengthOf(0) | ||
329 | expect(await list({ id: 1 })).to.have.lengthOf(1) | ||
330 | |||
331 | expect(await list({ search: 'my super name for server 1' })).to.have.lengthOf(3) | ||
332 | expect(await list({ search: 'aaaaaaaaaaaaaaaaaaaaaaaaaa' })).to.have.lengthOf(0) | ||
333 | |||
334 | expect(await list({ searchVideo: 'my second super name for server 1' })).to.have.lengthOf(1) | ||
335 | |||
336 | expect(await list({ searchVideoChannel: 'root' })).to.have.lengthOf(3) | ||
337 | expect(await list({ searchVideoChannel: 'aaaa' })).to.have.lengthOf(0) | ||
338 | |||
339 | expect(await list({ searchReporter: 'user2' })).to.have.lengthOf(1) | ||
340 | expect(await list({ searchReporter: 'root' })).to.have.lengthOf(4) | ||
341 | |||
342 | expect(await list({ searchReportee: 'root' })).to.have.lengthOf(3) | ||
343 | expect(await list({ searchReportee: 'aaaa' })).to.have.lengthOf(0) | ||
344 | |||
345 | expect(await list({ videoIs: 'deleted' })).to.have.lengthOf(1) | ||
346 | expect(await list({ videoIs: 'blacklisted' })).to.have.lengthOf(0) | ||
347 | |||
348 | expect(await list({ state: VideoAbuseState.ACCEPTED })).to.have.lengthOf(0) | ||
349 | expect(await list({ state: VideoAbuseState.PENDING })).to.have.lengthOf(5) | ||
242 | }) | 350 | }) |
243 | 351 | ||
244 | after(async function () { | 352 | after(async function () { |
diff --git a/server/tests/api/videos/video-blacklist.ts b/server/tests/api/videos/video-blacklist.ts index 854b2f0cb..67bc0114c 100644 --- a/server/tests/api/videos/video-blacklist.ts +++ b/server/tests/api/videos/video-blacklist.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import { orderBy } from 'lodash' | 4 | import { orderBy } from 'lodash' |
@@ -8,7 +8,8 @@ import { | |||
8 | cleanupTests, | 8 | cleanupTests, |
9 | createUser, | 9 | createUser, |
10 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
11 | getBlacklistedVideosList, getMyUserInformation, | 11 | getBlacklistedVideosList, |
12 | getMyUserInformation, | ||
12 | getMyVideos, | 13 | getMyVideos, |
13 | getVideosList, | 14 | getVideosList, |
14 | killallServers, | 15 | killallServers, |
@@ -17,7 +18,6 @@ import { | |||
17 | searchVideo, | 18 | searchVideo, |
18 | ServerInfo, | 19 | ServerInfo, |
19 | setAccessTokensToServers, | 20 | setAccessTokensToServers, |
20 | setDefaultVideoChannel, | ||
21 | updateVideo, | 21 | updateVideo, |
22 | updateVideoBlacklist, | 22 | updateVideoBlacklist, |
23 | uploadVideo, | 23 | uploadVideo, |
@@ -27,7 +27,7 @@ import { doubleFollow } from '../../../../shared/extra-utils/server/follows' | |||
27 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 27 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
28 | import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos' | 28 | import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos' |
29 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | 29 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' |
30 | import { User, UserRole, UserUpdateMe } from '../../../../shared/models/users' | 30 | import { User, UserRole } from '../../../../shared/models/users' |
31 | import { getMagnetURI, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' | 31 | import { getMagnetURI, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' |
32 | 32 | ||
33 | const expect = chai.expect | 33 | const expect = chai.expect |
@@ -40,7 +40,7 @@ describe('Test video blacklist', function () { | |||
40 | const res = await getVideosList(server.url) | 40 | const res = await getVideosList(server.url) |
41 | 41 | ||
42 | const videos = res.body.data | 42 | const videos = res.body.data |
43 | for (let video of videos) { | 43 | for (const video of videos) { |
44 | await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason') | 44 | await addVideoToBlacklist(server.url, server.accessToken, video.id, 'super reason') |
45 | } | 45 | } |
46 | } | 46 | } |
@@ -72,7 +72,7 @@ describe('Test video blacklist', function () { | |||
72 | 72 | ||
73 | it('Should not have the video blacklisted in videos list/search on server 1', async function () { | 73 | it('Should not have the video blacklisted in videos list/search on server 1', async function () { |
74 | { | 74 | { |
75 | const res = await getVideosList(servers[ 0 ].url) | 75 | const res = await getVideosList(servers[0].url) |
76 | 76 | ||
77 | expect(res.body.total).to.equal(0) | 77 | expect(res.body.total).to.equal(0) |
78 | expect(res.body.data).to.be.an('array') | 78 | expect(res.body.data).to.be.an('array') |
@@ -80,7 +80,7 @@ describe('Test video blacklist', function () { | |||
80 | } | 80 | } |
81 | 81 | ||
82 | { | 82 | { |
83 | const res = await searchVideo(servers[ 0 ].url, 'name') | 83 | const res = await searchVideo(servers[0].url, 'name') |
84 | 84 | ||
85 | expect(res.body.total).to.equal(0) | 85 | expect(res.body.total).to.equal(0) |
86 | expect(res.body.data).to.be.an('array') | 86 | expect(res.body.data).to.be.an('array') |
@@ -90,7 +90,7 @@ describe('Test video blacklist', function () { | |||
90 | 90 | ||
91 | it('Should have the blacklisted video in videos list/search on server 2', async function () { | 91 | it('Should have the blacklisted video in videos list/search on server 2', async function () { |
92 | { | 92 | { |
93 | const res = await getVideosList(servers[ 1 ].url) | 93 | const res = await getVideosList(servers[1].url) |
94 | 94 | ||
95 | expect(res.body.total).to.equal(2) | 95 | expect(res.body.total).to.equal(2) |
96 | expect(res.body.data).to.be.an('array') | 96 | expect(res.body.data).to.be.an('array') |
@@ -98,7 +98,7 @@ describe('Test video blacklist', function () { | |||
98 | } | 98 | } |
99 | 99 | ||
100 | { | 100 | { |
101 | const res = await searchVideo(servers[ 1 ].url, 'video') | 101 | const res = await searchVideo(servers[1].url, 'video') |
102 | 102 | ||
103 | expect(res.body.total).to.equal(2) | 103 | expect(res.body.total).to.equal(2) |
104 | expect(res.body.data).to.be.an('array') | 104 | expect(res.body.data).to.be.an('array') |
@@ -125,8 +125,8 @@ describe('Test video blacklist', function () { | |||
125 | 125 | ||
126 | it('Should display all the blacklisted videos when applying manual type filter', async function () { | 126 | it('Should display all the blacklisted videos when applying manual type filter', async function () { |
127 | const res = await getBlacklistedVideosList({ | 127 | const res = await getBlacklistedVideosList({ |
128 | url: servers[ 0 ].url, | 128 | url: servers[0].url, |
129 | token: servers[ 0 ].accessToken, | 129 | token: servers[0].accessToken, |
130 | type: VideoBlacklistType.MANUAL | 130 | type: VideoBlacklistType.MANUAL |
131 | }) | 131 | }) |
132 | 132 | ||
@@ -139,8 +139,8 @@ describe('Test video blacklist', function () { | |||
139 | 139 | ||
140 | it('Should display nothing when applying automatic type filter', async function () { | 140 | it('Should display nothing when applying automatic type filter', async function () { |
141 | const res = await getBlacklistedVideosList({ | 141 | const res = await getBlacklistedVideosList({ |
142 | url: servers[ 0 ].url, | 142 | url: servers[0].url, |
143 | token: servers[ 0 ].accessToken, | 143 | token: servers[0].accessToken, |
144 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | 144 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED |
145 | }) | 145 | }) |
146 | 146 | ||
@@ -152,7 +152,7 @@ describe('Test video blacklist', function () { | |||
152 | }) | 152 | }) |
153 | 153 | ||
154 | it('Should get the correct sort when sorting by descending id', async function () { | 154 | it('Should get the correct sort when sorting by descending id', async function () { |
155 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-id' }) | 155 | const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-id' }) |
156 | expect(res.body.total).to.equal(2) | 156 | expect(res.body.total).to.equal(2) |
157 | 157 | ||
158 | const blacklistedVideos = res.body.data | 158 | const blacklistedVideos = res.body.data |
@@ -165,7 +165,7 @@ describe('Test video blacklist', function () { | |||
165 | }) | 165 | }) |
166 | 166 | ||
167 | it('Should get the correct sort when sorting by descending video name', async function () { | 167 | it('Should get the correct sort when sorting by descending video name', async function () { |
168 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) | 168 | const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' }) |
169 | expect(res.body.total).to.equal(2) | 169 | expect(res.body.total).to.equal(2) |
170 | 170 | ||
171 | const blacklistedVideos = res.body.data | 171 | const blacklistedVideos = res.body.data |
@@ -178,7 +178,7 @@ describe('Test video blacklist', function () { | |||
178 | }) | 178 | }) |
179 | 179 | ||
180 | it('Should get the correct sort when sorting by ascending creation date', async function () { | 180 | it('Should get the correct sort when sorting by ascending creation date', async function () { |
181 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' }) | 181 | const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: 'createdAt' }) |
182 | expect(res.body.total).to.equal(2) | 182 | expect(res.body.total).to.equal(2) |
183 | 183 | ||
184 | const blacklistedVideos = res.body.data | 184 | const blacklistedVideos = res.body.data |
@@ -195,7 +195,7 @@ describe('Test video blacklist', function () { | |||
195 | it('Should change the reason', async function () { | 195 | it('Should change the reason', async function () { |
196 | await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated') | 196 | await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated') |
197 | 197 | ||
198 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) | 198 | const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' }) |
199 | const video = res.body.data.find(b => b.video.id === videoId) | 199 | const video = res.body.data.find(b => b.video.id === videoId) |
200 | 200 | ||
201 | expect(video.reason).to.equal('my super reason updated') | 201 | expect(video.reason).to.equal('my super reason updated') |
@@ -231,7 +231,7 @@ describe('Test video blacklist', function () { | |||
231 | 231 | ||
232 | it('Should remove a video from the blacklist on server 1', async function () { | 232 | it('Should remove a video from the blacklist on server 1', async function () { |
233 | // Get one video in the blacklist | 233 | // Get one video in the blacklist |
234 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) | 234 | const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' }) |
235 | videoToRemove = res.body.data[0] | 235 | videoToRemove = res.body.data[0] |
236 | blacklist = res.body.data.slice(1) | 236 | blacklist = res.body.data.slice(1) |
237 | 237 | ||
@@ -252,7 +252,7 @@ describe('Test video blacklist', function () { | |||
252 | }) | 252 | }) |
253 | 253 | ||
254 | it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () { | 254 | it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () { |
255 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) | 255 | const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: '-name' }) |
256 | expect(res.body.total).to.equal(1) | 256 | expect(res.body.total).to.equal(1) |
257 | 257 | ||
258 | const videos = res.body.data | 258 | const videos = res.body.data |
@@ -274,7 +274,7 @@ describe('Test video blacklist', function () { | |||
274 | video3UUID = res.body.video.uuid | 274 | video3UUID = res.body.video.uuid |
275 | } | 275 | } |
276 | { | 276 | { |
277 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'Video 4' }) | 277 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'Video 4' }) |
278 | video4UUID = res.body.video.uuid | 278 | video4UUID = res.body.video.uuid |
279 | } | 279 | } |
280 | 280 | ||
@@ -284,17 +284,17 @@ describe('Test video blacklist', function () { | |||
284 | it('Should blacklist video 3 and keep it federated', async function () { | 284 | it('Should blacklist video 3 and keep it federated', async function () { |
285 | this.timeout(10000) | 285 | this.timeout(10000) |
286 | 286 | ||
287 | await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video3UUID, 'super reason', false) | 287 | await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video3UUID, 'super reason', false) |
288 | 288 | ||
289 | await waitJobs(servers) | 289 | await waitJobs(servers) |
290 | 290 | ||
291 | { | 291 | { |
292 | const res = await getVideosList(servers[ 0 ].url) | 292 | const res = await getVideosList(servers[0].url) |
293 | expect(res.body.data.find(v => v.uuid === video3UUID)).to.be.undefined | 293 | expect(res.body.data.find(v => v.uuid === video3UUID)).to.be.undefined |
294 | } | 294 | } |
295 | 295 | ||
296 | { | 296 | { |
297 | const res = await getVideosList(servers[ 1 ].url) | 297 | const res = await getVideosList(servers[1].url) |
298 | expect(res.body.data.find(v => v.uuid === video3UUID)).to.not.be.undefined | 298 | expect(res.body.data.find(v => v.uuid === video3UUID)).to.not.be.undefined |
299 | } | 299 | } |
300 | }) | 300 | }) |
@@ -302,7 +302,7 @@ describe('Test video blacklist', function () { | |||
302 | it('Should unfederate the video', async function () { | 302 | it('Should unfederate the video', async function () { |
303 | this.timeout(10000) | 303 | this.timeout(10000) |
304 | 304 | ||
305 | await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID, 'super reason', true) | 305 | await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video4UUID, 'super reason', true) |
306 | 306 | ||
307 | await waitJobs(servers) | 307 | await waitJobs(servers) |
308 | 308 | ||
@@ -315,7 +315,7 @@ describe('Test video blacklist', function () { | |||
315 | it('Should have the video unfederated even after an Update AP message', async function () { | 315 | it('Should have the video unfederated even after an Update AP message', async function () { |
316 | this.timeout(10000) | 316 | this.timeout(10000) |
317 | 317 | ||
318 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID, { description: 'super description' }) | 318 | await updateVideo(servers[0].url, servers[0].accessToken, video4UUID, { description: 'super description' }) |
319 | 319 | ||
320 | await waitJobs(servers) | 320 | await waitJobs(servers) |
321 | 321 | ||
@@ -326,7 +326,7 @@ describe('Test video blacklist', function () { | |||
326 | }) | 326 | }) |
327 | 327 | ||
328 | it('Should have the correct video blacklist unfederate attribute', async function () { | 328 | it('Should have the correct video blacklist unfederate attribute', async function () { |
329 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' }) | 329 | const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, sort: 'createdAt' }) |
330 | 330 | ||
331 | const blacklistedVideos: VideoBlacklist[] = res.body.data | 331 | const blacklistedVideos: VideoBlacklist[] = res.body.data |
332 | const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID) | 332 | const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID) |
@@ -339,7 +339,7 @@ describe('Test video blacklist', function () { | |||
339 | it('Should remove the video from blacklist and refederate the video', async function () { | 339 | it('Should remove the video from blacklist and refederate the video', async function () { |
340 | this.timeout(10000) | 340 | this.timeout(10000) |
341 | 341 | ||
342 | await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video4UUID) | 342 | await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video4UUID) |
343 | 343 | ||
344 | await waitJobs(servers) | 344 | await waitJobs(servers) |
345 | 345 | ||
@@ -362,9 +362,9 @@ describe('Test video blacklist', function () { | |||
362 | killallServers([ servers[0] ]) | 362 | killallServers([ servers[0] ]) |
363 | 363 | ||
364 | const config = { | 364 | const config = { |
365 | 'auto_blacklist': { | 365 | auto_blacklist: { |
366 | videos: { | 366 | videos: { |
367 | 'of_users': { | 367 | of_users: { |
368 | enabled: true | 368 | enabled: true |
369 | } | 369 | } |
370 | } | 370 | } |
@@ -375,8 +375,8 @@ describe('Test video blacklist', function () { | |||
375 | { | 375 | { |
376 | const user = { username: 'user_without_flag', password: 'password' } | 376 | const user = { username: 'user_without_flag', password: 'password' } |
377 | await createUser({ | 377 | await createUser({ |
378 | url: servers[ 0 ].url, | 378 | url: servers[0].url, |
379 | accessToken: servers[ 0 ].accessToken, | 379 | accessToken: servers[0].accessToken, |
380 | username: user.username, | 380 | username: user.username, |
381 | adminFlags: UserAdminFlag.NONE, | 381 | adminFlags: UserAdminFlag.NONE, |
382 | password: user.password, | 382 | password: user.password, |
@@ -393,8 +393,8 @@ describe('Test video blacklist', function () { | |||
393 | { | 393 | { |
394 | const user = { username: 'user_with_flag', password: 'password' } | 394 | const user = { username: 'user_with_flag', password: 'password' } |
395 | await createUser({ | 395 | await createUser({ |
396 | url: servers[ 0 ].url, | 396 | url: servers[0].url, |
397 | accessToken: servers[ 0 ].accessToken, | 397 | accessToken: servers[0].accessToken, |
398 | username: user.username, | 398 | username: user.username, |
399 | adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST, | 399 | adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST, |
400 | password: user.password, | 400 | password: user.password, |
@@ -411,8 +411,8 @@ describe('Test video blacklist', function () { | |||
411 | await uploadVideo(servers[0].url, userWithoutFlag, { name: 'blacklisted' }) | 411 | await uploadVideo(servers[0].url, userWithoutFlag, { name: 'blacklisted' }) |
412 | 412 | ||
413 | const res = await getBlacklistedVideosList({ | 413 | const res = await getBlacklistedVideosList({ |
414 | url: servers[ 0 ].url, | 414 | url: servers[0].url, |
415 | token: servers[ 0 ].accessToken, | 415 | token: servers[0].accessToken, |
416 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | 416 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED |
417 | }) | 417 | }) |
418 | 418 | ||
@@ -428,11 +428,11 @@ describe('Test video blacklist', function () { | |||
428 | name: 'URL import', | 428 | name: 'URL import', |
429 | channelId: channelOfUserWithoutFlag | 429 | channelId: channelOfUserWithoutFlag |
430 | } | 430 | } |
431 | await importVideo(servers[ 0 ].url, userWithoutFlag, attributes) | 431 | await importVideo(servers[0].url, userWithoutFlag, attributes) |
432 | 432 | ||
433 | const res = await getBlacklistedVideosList({ | 433 | const res = await getBlacklistedVideosList({ |
434 | url: servers[ 0 ].url, | 434 | url: servers[0].url, |
435 | token: servers[ 0 ].accessToken, | 435 | token: servers[0].accessToken, |
436 | sort: 'createdAt', | 436 | sort: 'createdAt', |
437 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | 437 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED |
438 | }) | 438 | }) |
@@ -447,11 +447,11 @@ describe('Test video blacklist', function () { | |||
447 | name: 'Torrent import', | 447 | name: 'Torrent import', |
448 | channelId: channelOfUserWithoutFlag | 448 | channelId: channelOfUserWithoutFlag |
449 | } | 449 | } |
450 | await importVideo(servers[ 0 ].url, userWithoutFlag, attributes) | 450 | await importVideo(servers[0].url, userWithoutFlag, attributes) |
451 | 451 | ||
452 | const res = await getBlacklistedVideosList({ | 452 | const res = await getBlacklistedVideosList({ |
453 | url: servers[ 0 ].url, | 453 | url: servers[0].url, |
454 | token: servers[ 0 ].accessToken, | 454 | token: servers[0].accessToken, |
455 | sort: 'createdAt', | 455 | sort: 'createdAt', |
456 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | 456 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED |
457 | }) | 457 | }) |
@@ -464,8 +464,8 @@ describe('Test video blacklist', function () { | |||
464 | await uploadVideo(servers[0].url, userWithFlag, { name: 'not blacklisted' }) | 464 | await uploadVideo(servers[0].url, userWithFlag, { name: 'not blacklisted' }) |
465 | 465 | ||
466 | const res = await getBlacklistedVideosList({ | 466 | const res = await getBlacklistedVideosList({ |
467 | url: servers[ 0 ].url, | 467 | url: servers[0].url, |
468 | token: servers[ 0 ].accessToken, | 468 | token: servers[0].accessToken, |
469 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | 469 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED |
470 | }) | 470 | }) |
471 | 471 | ||
diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts index 5e13f5949..b4ecb39f4 100644 --- a/server/tests/api/videos/video-captions.ts +++ b/server/tests/api/videos/video-captions.ts | |||
@@ -1,16 +1,17 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | checkVideoFilesWereRemoved, cleanupTests, | 6 | checkVideoFilesWereRemoved, |
7 | cleanupTests, | ||
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
9 | removeVideo, | 10 | removeVideo, |
10 | uploadVideo, | 11 | uploadVideo, |
11 | wait | 12 | wait |
12 | } from '../../../../shared/extra-utils' | 13 | } from '../../../../shared/extra-utils' |
13 | import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' | 14 | import { ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' |
14 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 15 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
15 | import { | 16 | import { |
16 | createVideoCaption, | 17 | createVideoCaption, |
@@ -36,7 +37,7 @@ describe('Test video captions', function () { | |||
36 | 37 | ||
37 | await waitJobs(servers) | 38 | await waitJobs(servers) |
38 | 39 | ||
39 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'my video name' }) | 40 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'my video name' }) |
40 | videoUUID = res.body.video.uuid | 41 | videoUUID = res.body.video.uuid |
41 | 42 | ||
42 | await waitJobs(servers) | 43 | await waitJobs(servers) |
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts index 64ee2355a..dee6575b9 100644 --- a/server/tests/api/videos/video-change-ownership.ts +++ b/server/tests/api/videos/video-change-ownership.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -38,7 +38,7 @@ describe('Test video change ownership - nominal', function () { | |||
38 | } | 38 | } |
39 | let firstUserAccessToken = '' | 39 | let firstUserAccessToken = '' |
40 | let secondUserAccessToken = '' | 40 | let secondUserAccessToken = '' |
41 | let lastRequestChangeOwnershipId = undefined | 41 | let lastRequestChangeOwnershipId = '' |
42 | 42 | ||
43 | before(async function () { | 43 | before(async function () { |
44 | this.timeout(50000) | 44 | this.timeout(50000) |
@@ -48,15 +48,15 @@ describe('Test video change ownership - nominal', function () { | |||
48 | 48 | ||
49 | const videoQuota = 42000000 | 49 | const videoQuota = 42000000 |
50 | await createUser({ | 50 | await createUser({ |
51 | url: servers[ 0 ].url, | 51 | url: servers[0].url, |
52 | accessToken: servers[ 0 ].accessToken, | 52 | accessToken: servers[0].accessToken, |
53 | username: firstUser.username, | 53 | username: firstUser.username, |
54 | password: firstUser.password, | 54 | password: firstUser.password, |
55 | videoQuota: videoQuota | 55 | videoQuota: videoQuota |
56 | }) | 56 | }) |
57 | await createUser({ | 57 | await createUser({ |
58 | url: servers[ 0 ].url, | 58 | url: servers[0].url, |
59 | accessToken: servers[ 0 ].accessToken, | 59 | accessToken: servers[0].accessToken, |
60 | username: secondUser.username, | 60 | username: secondUser.username, |
61 | password: secondUser.password, | 61 | password: secondUser.password, |
62 | videoQuota: videoQuota | 62 | videoQuota: videoQuota |
@@ -209,7 +209,7 @@ describe('Test video change ownership - nominal', function () { | |||
209 | }) | 209 | }) |
210 | 210 | ||
211 | describe('Test video change ownership - quota too small', function () { | 211 | describe('Test video change ownership - quota too small', function () { |
212 | let server: ServerInfo = undefined | 212 | let server: ServerInfo |
213 | const firstUser = { | 213 | const firstUser = { |
214 | username: 'first', | 214 | username: 'first', |
215 | password: 'My great password' | 215 | password: 'My great password' |
@@ -220,14 +220,14 @@ describe('Test video change ownership - quota too small', function () { | |||
220 | } | 220 | } |
221 | let firstUserAccessToken = '' | 221 | let firstUserAccessToken = '' |
222 | let secondUserAccessToken = '' | 222 | let secondUserAccessToken = '' |
223 | let lastRequestChangeOwnershipId = undefined | 223 | let lastRequestChangeOwnershipId = '' |
224 | 224 | ||
225 | before(async function () { | 225 | before(async function () { |
226 | this.timeout(50000) | 226 | this.timeout(50000) |
227 | 227 | ||
228 | // Run one server | 228 | // Run one server |
229 | server = await flushAndRunServer(1) | 229 | server = await flushAndRunServer(1) |
230 | await setAccessTokensToServers([server]) | 230 | await setAccessTokensToServers([ server ]) |
231 | 231 | ||
232 | const videoQuota = 42000000 | 232 | const videoQuota = 42000000 |
233 | const limitedVideoQuota = 10 | 233 | const limitedVideoQuota = 10 |
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts index 4f600cae8..876a6ab66 100644 --- a/server/tests/api/videos/video-channels.ts +++ b/server/tests/api/videos/video-channels.ts | |||
@@ -1,19 +1,21 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { User, Video, VideoChannel, VideoDetails } from '../../../../shared/index' | 5 | import { User, Video, VideoChannel, ViewsPerDate, VideoDetails } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | cleanupTests, | 7 | cleanupTests, |
8 | createUser, | 8 | createUser, |
9 | doubleFollow, | 9 | doubleFollow, |
10 | flushAndRunMultipleServers, getVideo, | 10 | flushAndRunMultipleServers, |
11 | getVideo, | ||
11 | getVideoChannelVideos, | 12 | getVideoChannelVideos, |
12 | testImage, | 13 | testImage, |
13 | updateVideo, | 14 | updateVideo, |
14 | updateVideoChannelAvatar, | 15 | updateVideoChannelAvatar, |
15 | uploadVideo, | 16 | uploadVideo, |
16 | userLogin | 17 | userLogin, |
18 | wait | ||
17 | } from '../../../../shared/extra-utils' | 19 | } from '../../../../shared/extra-utils' |
18 | import { | 20 | import { |
19 | addVideoChannel, | 21 | addVideoChannel, |
@@ -24,7 +26,8 @@ import { | |||
24 | getVideoChannelsList, | 26 | getVideoChannelsList, |
25 | ServerInfo, | 27 | ServerInfo, |
26 | setAccessTokensToServers, | 28 | setAccessTokensToServers, |
27 | updateVideoChannel | 29 | updateVideoChannel, |
30 | viewVideo | ||
28 | } from '../../../../shared/extra-utils/index' | 31 | } from '../../../../shared/extra-utils/index' |
29 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 32 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
30 | 33 | ||
@@ -73,14 +76,14 @@ describe('Test video channels', function () { | |||
73 | description: 'super video channel description', | 76 | description: 'super video channel description', |
74 | support: 'super video channel support text' | 77 | support: 'super video channel support text' |
75 | } | 78 | } |
76 | const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) | 79 | const res = await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel) |
77 | secondVideoChannelId = res.body.videoChannel.id | 80 | secondVideoChannelId = res.body.videoChannel.id |
78 | } | 81 | } |
79 | 82 | ||
80 | // The channel is 1 is propagated to servers 2 | 83 | // The channel is 1 is propagated to servers 2 |
81 | { | 84 | { |
82 | const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' } | 85 | const videoAttributesArg = { name: 'my video name', channelId: secondVideoChannelId, support: 'video support field' } |
83 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributesArg) | 86 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributesArg) |
84 | videoUUID = res.body.video.uuid | 87 | videoUUID = res.body.video.uuid |
85 | } | 88 | } |
86 | 89 | ||
@@ -106,7 +109,7 @@ describe('Test video channels', function () { | |||
106 | 109 | ||
107 | it('Should have two video channels when getting account channels on server 1', async function () { | 110 | it('Should have two video channels when getting account channels on server 1', async function () { |
108 | const res = await getAccountVideoChannelsList({ | 111 | const res = await getAccountVideoChannelsList({ |
109 | url: servers[ 0 ].url, | 112 | url: servers[0].url, |
110 | accountName: userInfo.account.name + '@' + userInfo.account.host | 113 | accountName: userInfo.account.name + '@' + userInfo.account.host |
111 | }) | 114 | }) |
112 | 115 | ||
@@ -127,7 +130,7 @@ describe('Test video channels', function () { | |||
127 | it('Should paginate and sort account channels', async function () { | 130 | it('Should paginate and sort account channels', async function () { |
128 | { | 131 | { |
129 | const res = await getAccountVideoChannelsList({ | 132 | const res = await getAccountVideoChannelsList({ |
130 | url: servers[ 0 ].url, | 133 | url: servers[0].url, |
131 | accountName: userInfo.account.name + '@' + userInfo.account.host, | 134 | accountName: userInfo.account.name + '@' + userInfo.account.host, |
132 | start: 0, | 135 | start: 0, |
133 | count: 1, | 136 | count: 1, |
@@ -137,13 +140,13 @@ describe('Test video channels', function () { | |||
137 | expect(res.body.total).to.equal(2) | 140 | expect(res.body.total).to.equal(2) |
138 | expect(res.body.data).to.have.lengthOf(1) | 141 | expect(res.body.data).to.have.lengthOf(1) |
139 | 142 | ||
140 | const videoChannel: VideoChannel = res.body.data[ 0 ] | 143 | const videoChannel: VideoChannel = res.body.data[0] |
141 | expect(videoChannel.name).to.equal('root_channel') | 144 | expect(videoChannel.name).to.equal('root_channel') |
142 | } | 145 | } |
143 | 146 | ||
144 | { | 147 | { |
145 | const res = await getAccountVideoChannelsList({ | 148 | const res = await getAccountVideoChannelsList({ |
146 | url: servers[ 0 ].url, | 149 | url: servers[0].url, |
147 | accountName: userInfo.account.name + '@' + userInfo.account.host, | 150 | accountName: userInfo.account.name + '@' + userInfo.account.host, |
148 | start: 0, | 151 | start: 0, |
149 | count: 1, | 152 | count: 1, |
@@ -153,13 +156,13 @@ describe('Test video channels', function () { | |||
153 | expect(res.body.total).to.equal(2) | 156 | expect(res.body.total).to.equal(2) |
154 | expect(res.body.data).to.have.lengthOf(1) | 157 | expect(res.body.data).to.have.lengthOf(1) |
155 | 158 | ||
156 | const videoChannel: VideoChannel = res.body.data[ 0 ] | 159 | const videoChannel: VideoChannel = res.body.data[0] |
157 | expect(videoChannel.name).to.equal('second_video_channel') | 160 | expect(videoChannel.name).to.equal('second_video_channel') |
158 | } | 161 | } |
159 | 162 | ||
160 | { | 163 | { |
161 | const res = await getAccountVideoChannelsList({ | 164 | const res = await getAccountVideoChannelsList({ |
162 | url: servers[ 0 ].url, | 165 | url: servers[0].url, |
163 | accountName: userInfo.account.name + '@' + userInfo.account.host, | 166 | accountName: userInfo.account.name + '@' + userInfo.account.host, |
164 | start: 1, | 167 | start: 1, |
165 | count: 1, | 168 | count: 1, |
@@ -169,14 +172,14 @@ describe('Test video channels', function () { | |||
169 | expect(res.body.total).to.equal(2) | 172 | expect(res.body.total).to.equal(2) |
170 | expect(res.body.data).to.have.lengthOf(1) | 173 | expect(res.body.data).to.have.lengthOf(1) |
171 | 174 | ||
172 | const videoChannel: VideoChannel = res.body.data[ 0 ] | 175 | const videoChannel: VideoChannel = res.body.data[0] |
173 | expect(videoChannel.name).to.equal('root_channel') | 176 | expect(videoChannel.name).to.equal('root_channel') |
174 | } | 177 | } |
175 | }) | 178 | }) |
176 | 179 | ||
177 | it('Should have one video channel when getting account channels on server 2', async function () { | 180 | it('Should have one video channel when getting account channels on server 2', async function () { |
178 | const res = await getAccountVideoChannelsList({ | 181 | const res = await getAccountVideoChannelsList({ |
179 | url: servers[ 1 ].url, | 182 | url: servers[1].url, |
180 | accountName: userInfo.account.name + '@' + userInfo.account.host | 183 | accountName: userInfo.account.name + '@' + userInfo.account.host |
181 | }) | 184 | }) |
182 | 185 | ||
@@ -349,19 +352,55 @@ describe('Test video channels', function () { | |||
349 | it('Should create the main channel with an uuid if there is a conflict', async function () { | 352 | it('Should create the main channel with an uuid if there is a conflict', async function () { |
350 | { | 353 | { |
351 | const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' } | 354 | const videoChannel = { name: 'toto_channel', displayName: 'My toto channel' } |
352 | await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, videoChannel) | 355 | await addVideoChannel(servers[0].url, servers[0].accessToken, videoChannel) |
353 | } | 356 | } |
354 | 357 | ||
355 | { | 358 | { |
356 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'toto', password: 'password' }) | 359 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: 'toto', password: 'password' }) |
357 | const accessToken = await userLogin(servers[ 0 ], { username: 'toto', password: 'password' }) | 360 | const accessToken = await userLogin(servers[0], { username: 'toto', password: 'password' }) |
358 | 361 | ||
359 | const res = await getMyUserInformation(servers[ 0 ].url, accessToken) | 362 | const res = await getMyUserInformation(servers[0].url, accessToken) |
360 | const videoChannel = res.body.videoChannels[ 0 ] | 363 | const videoChannel = res.body.videoChannels[0] |
361 | expect(videoChannel.name).to.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/) | 364 | expect(videoChannel.name).to.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/) |
362 | } | 365 | } |
363 | }) | 366 | }) |
364 | 367 | ||
368 | it('Should report correct channel statistics', async function () { | ||
369 | |||
370 | { | ||
371 | const res = await getAccountVideoChannelsList({ | ||
372 | url: servers[0].url, | ||
373 | accountName: userInfo.account.name + '@' + userInfo.account.host, | ||
374 | withStats: true | ||
375 | }) | ||
376 | res.body.data.forEach((channel: VideoChannel) => { | ||
377 | expect(channel).to.haveOwnProperty('viewsPerDay') | ||
378 | expect(channel.viewsPerDay).to.have.length(30 + 1) // daysPrior + today | ||
379 | channel.viewsPerDay.forEach((v: ViewsPerDate) => { | ||
380 | expect(v.date).to.be.an('string') | ||
381 | expect(v.views).to.equal(0) | ||
382 | }) | ||
383 | }) | ||
384 | } | ||
385 | |||
386 | { | ||
387 | // video has been posted on channel firstVideoChannelId since last update | ||
388 | await viewVideo(servers[0].url, videoUUID, 204, '0.0.0.1,127.0.0.1') | ||
389 | await viewVideo(servers[0].url, videoUUID, 204, '0.0.0.2,127.0.0.1') | ||
390 | |||
391 | // Wait the repeatable job | ||
392 | await wait(8000) | ||
393 | |||
394 | const res = await getAccountVideoChannelsList({ | ||
395 | url: servers[0].url, | ||
396 | accountName: userInfo.account.name + '@' + userInfo.account.host, | ||
397 | withStats: true | ||
398 | }) | ||
399 | const channelWithView = res.body.data.find((channel: VideoChannel) => channel.id === firstVideoChannelId) | ||
400 | expect(channelWithView.viewsPerDay.slice(-1)[0].views).to.equal(2) | ||
401 | } | ||
402 | }) | ||
403 | |||
365 | after(async function () { | 404 | after(async function () { |
366 | await cleanupTests(servers) | 405 | await cleanupTests(servers) |
367 | }) | 406 | }) |
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts index 06e4ff9c8..afb58e95a 100644 --- a/server/tests/api/videos/video-comments.ts +++ b/server/tests/api/videos/video-comments.ts | |||
@@ -1,17 +1,17 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 5 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' |
6 | import { cleanupTests, testImage } from '../../../../shared/extra-utils' | 6 | import { cleanupTests, testImage } from '../../../../shared/extra-utils' |
7 | import { | 7 | import { |
8 | createUser, | ||
8 | dateIsValid, | 9 | dateIsValid, |
9 | flushAndRunServer, | 10 | flushAndRunServer, |
11 | getAccessToken, | ||
10 | ServerInfo, | 12 | ServerInfo, |
11 | setAccessTokensToServers, | 13 | setAccessTokensToServers, |
12 | updateMyAvatar, | 14 | updateMyAvatar, |
13 | getAccessToken, | ||
14 | createUser, | ||
15 | uploadVideo | 15 | uploadVideo |
16 | } from '../../../../shared/extra-utils/index' | 16 | } from '../../../../shared/extra-utils/index' |
17 | import { | 17 | import { |
diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts index db4d278bf..b8e98e45f 100644 --- a/server/tests/api/videos/video-description.ts +++ b/server/tests/api/videos/video-description.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -8,7 +8,6 @@ import { | |||
8 | getVideo, | 8 | getVideo, |
9 | getVideoDescription, | 9 | getVideoDescription, |
10 | getVideosList, | 10 | getVideosList, |
11 | killallServers, | ||
12 | ServerInfo, | 11 | ServerInfo, |
13 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
14 | updateVideo, | 13 | updateVideo, |
@@ -23,7 +22,7 @@ describe('Test video description', function () { | |||
23 | let servers: ServerInfo[] = [] | 22 | let servers: ServerInfo[] = [] |
24 | let videoUUID = '' | 23 | let videoUUID = '' |
25 | let videoId: number | 24 | let videoId: number |
26 | let longDescription = 'my super description for server 1'.repeat(50) | 25 | const longDescription = 'my super description for server 1'.repeat(50) |
27 | 26 | ||
28 | before(async function () { | 27 | before(async function () { |
29 | this.timeout(40000) | 28 | this.timeout(40000) |
@@ -61,7 +60,7 @@ describe('Test video description', function () { | |||
61 | 60 | ||
62 | // 30 characters * 6 -> 240 characters | 61 | // 30 characters * 6 -> 240 characters |
63 | const truncatedDescription = 'my super description for server 1'.repeat(7) + | 62 | const truncatedDescription = 'my super description for server 1'.repeat(7) + |
64 | 'my super descrip...' | 63 | 'my super descrip...' |
65 | 64 | ||
66 | expect(video.description).to.equal(truncatedDescription) | 65 | expect(video.description).to.equal(truncatedDescription) |
67 | } | 66 | } |
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts index bde3b5656..6555bc8b6 100644 --- a/server/tests/api/videos/video-hls.ts +++ b/server/tests/api/videos/video-hls.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -10,13 +10,16 @@ import { | |||
10 | doubleFollow, | 10 | doubleFollow, |
11 | flushAndRunMultipleServers, | 11 | flushAndRunMultipleServers, |
12 | getPlaylist, | 12 | getPlaylist, |
13 | getVideo, makeGetRequest, makeRawRequest, | 13 | getVideo, |
14 | makeRawRequest, | ||
14 | removeVideo, | 15 | removeVideo, |
15 | ServerInfo, | 16 | ServerInfo, |
16 | setAccessTokensToServers, updateCustomSubConfig, | 17 | setAccessTokensToServers, |
18 | updateCustomSubConfig, | ||
17 | updateVideo, | 19 | updateVideo, |
18 | uploadVideo, | 20 | uploadVideo, |
19 | waitJobs, webtorrentAdd | 21 | waitJobs, |
22 | webtorrentAdd | ||
20 | } from '../../../../shared/extra-utils' | 23 | } from '../../../../shared/extra-utils' |
21 | import { VideoDetails } from '../../../../shared/models/videos' | 24 | import { VideoDetails } from '../../../../shared/models/videos' |
22 | import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' | 25 | import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' |
@@ -48,7 +51,9 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn | |||
48 | 51 | ||
49 | expect(file.magnetUri).to.have.lengthOf.above(2) | 52 | expect(file.magnetUri).to.have.lengthOf.above(2) |
50 | expect(file.torrentUrl).to.equal(`${baseUrl}/static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`) | 53 | expect(file.torrentUrl).to.equal(`${baseUrl}/static/torrents/${videoDetails.uuid}-${file.resolution.id}-hls.torrent`) |
51 | expect(file.fileUrl).to.equal(`${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4`) | 54 | expect(file.fileUrl).to.equal( |
55 | `${baseUrl}/static/streaming-playlists/hls/${videoDetails.uuid}/${videoDetails.uuid}-${file.resolution.id}-fragmented.mp4` | ||
56 | ) | ||
52 | expect(file.resolution.label).to.equal(resolution + 'p') | 57 | expect(file.resolution.label).to.equal(resolution + 'p') |
53 | 58 | ||
54 | await makeRawRequest(file.torrentUrl, 200) | 59 | await makeRawRequest(file.torrentUrl, 200) |
@@ -66,7 +71,9 @@ async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string, hlsOn | |||
66 | const masterPlaylist = res.text | 71 | const masterPlaylist = res.text |
67 | 72 | ||
68 | for (const resolution of resolutions) { | 73 | for (const resolution of resolutions) { |
69 | const reg = new RegExp('#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+,CODECS="avc1.64001f,mp4a.40.2"') | 74 | const reg = new RegExp( |
75 | '#EXT-X-STREAM-INF:BANDWIDTH=\\d+,RESOLUTION=\\d+x' + resolution + ',FRAME-RATE=\\d+,CODECS="avc1.64001f,mp4a.40.2"' | ||
76 | ) | ||
70 | 77 | ||
71 | expect(masterPlaylist).to.match(reg) | 78 | expect(masterPlaylist).to.match(reg) |
72 | expect(masterPlaylist).to.contain(`${resolution}.m3u8`) | 79 | expect(masterPlaylist).to.contain(`${resolution}.m3u8`) |
@@ -102,7 +109,7 @@ describe('Test HLS videos', function () { | |||
102 | it('Should upload a video and transcode it to HLS', async function () { | 109 | it('Should upload a video and transcode it to HLS', async function () { |
103 | this.timeout(120000) | 110 | this.timeout(120000) |
104 | 111 | ||
105 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1', fixture: 'video_short.webm' }) | 112 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video 1', fixture: 'video_short.webm' }) |
106 | videoUUID = res.body.video.uuid | 113 | videoUUID = res.body.video.uuid |
107 | 114 | ||
108 | await waitJobs(servers) | 115 | await waitJobs(servers) |
@@ -113,7 +120,7 @@ describe('Test HLS videos', function () { | |||
113 | it('Should upload an audio file and transcode it to HLS', async function () { | 120 | it('Should upload an audio file and transcode it to HLS', async function () { |
114 | this.timeout(120000) | 121 | this.timeout(120000) |
115 | 122 | ||
116 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video audio', fixture: 'sample.ogg' }) | 123 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video audio', fixture: 'sample.ogg' }) |
117 | videoAudioUUID = res.body.video.uuid | 124 | videoAudioUUID = res.body.video.uuid |
118 | 125 | ||
119 | await waitJobs(servers) | 126 | await waitJobs(servers) |
@@ -124,7 +131,7 @@ describe('Test HLS videos', function () { | |||
124 | it('Should update the video', async function () { | 131 | it('Should update the video', async function () { |
125 | this.timeout(10000) | 132 | this.timeout(10000) |
126 | 133 | ||
127 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID, { name: 'video 1 updated' }) | 134 | await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video 1 updated' }) |
128 | 135 | ||
129 | await waitJobs(servers) | 136 | await waitJobs(servers) |
130 | 137 | ||
@@ -134,8 +141,8 @@ describe('Test HLS videos', function () { | |||
134 | it('Should delete videos', async function () { | 141 | it('Should delete videos', async function () { |
135 | this.timeout(10000) | 142 | this.timeout(10000) |
136 | 143 | ||
137 | await removeVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoUUID) | 144 | await removeVideo(servers[0].url, servers[0].accessToken, videoUUID) |
138 | await removeVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAudioUUID) | 145 | await removeVideo(servers[0].url, servers[0].accessToken, videoAudioUUID) |
139 | 146 | ||
140 | await waitJobs(servers) | 147 | await waitJobs(servers) |
141 | 148 | ||
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts index 1233ed6eb..4d5989f43 100644 --- a/server/tests/api/videos/video-imports.ts +++ b/server/tests/api/videos/video-imports.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoDetails, VideoImport, VideoPrivacy } from '../../../../shared/models/videos' | 5 | import { VideoDetails, VideoImport, VideoPrivacy, VideoCaption } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | cleanupTests, | 7 | cleanupTests, |
8 | doubleFollow, | 8 | doubleFollow, |
@@ -11,13 +11,15 @@ import { | |||
11 | getMyVideos, | 11 | getMyVideos, |
12 | getVideo, | 12 | getVideo, |
13 | getVideosList, | 13 | getVideosList, |
14 | listVideoCaptions, | ||
15 | testCaptionFile, | ||
14 | immutableAssign, | 16 | immutableAssign, |
15 | killallServers, | ||
16 | ServerInfo, | 17 | ServerInfo, |
17 | setAccessTokensToServers | 18 | setAccessTokensToServers |
18 | } from '../../../../shared/extra-utils' | 19 | } from '../../../../shared/extra-utils' |
19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 20 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
20 | import { getMagnetURI, getYoutubeVideoUrl, importVideo, getMyVideoImports } from '../../../../shared/extra-utils/videos/video-imports' | 21 | import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' |
22 | import { testImage } from '../../../../shared/extra-utils/miscs/miscs' | ||
21 | 23 | ||
22 | const expect = chai.expect | 24 | const expect = chai.expect |
23 | 25 | ||
@@ -61,11 +63,14 @@ describe('Test video imports', function () { | |||
61 | 63 | ||
62 | expect(videoTorrent.name).to.contain('ä½ å¥½ 世界 720p.mp4') | 64 | expect(videoTorrent.name).to.contain('ä½ å¥½ 世界 720p.mp4') |
63 | expect(videoMagnet.name).to.contain('super peertube2 video') | 65 | expect(videoMagnet.name).to.contain('super peertube2 video') |
66 | |||
67 | const resCaptions = await listVideoCaptions(url, idHttp) | ||
68 | expect(resCaptions.body.total).to.equal(2) | ||
64 | } | 69 | } |
65 | 70 | ||
66 | async function checkVideoServer2 (url: string, id: number | string) { | 71 | async function checkVideoServer2 (url: string, id: number | string) { |
67 | const res = await getVideo(url, id) | 72 | const res = await getVideo(url, id) |
68 | const video = res.body | 73 | const video: VideoDetails = res.body |
69 | 74 | ||
70 | expect(video.name).to.equal('my super name') | 75 | expect(video.name).to.equal('my super name') |
71 | expect(video.category.label).to.equal('Entertainment') | 76 | expect(video.category.label).to.equal('Entertainment') |
@@ -76,6 +81,9 @@ describe('Test video imports', function () { | |||
76 | expect(video.tags).to.deep.equal([ 'supertag1', 'supertag2' ]) | 81 | expect(video.tags).to.deep.equal([ 'supertag1', 'supertag2' ]) |
77 | 82 | ||
78 | expect(video.files).to.have.lengthOf(1) | 83 | expect(video.files).to.have.lengthOf(1) |
84 | |||
85 | const resCaptions = await listVideoCaptions(url, id) | ||
86 | expect(resCaptions.body.total).to.equal(2) | ||
79 | } | 87 | } |
80 | 88 | ||
81 | before(async function () { | 89 | before(async function () { |
@@ -88,12 +96,12 @@ describe('Test video imports', function () { | |||
88 | 96 | ||
89 | { | 97 | { |
90 | const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) | 98 | const res = await getMyUserInformation(servers[0].url, servers[0].accessToken) |
91 | channelIdServer1 = res.body.videoChannels[ 0 ].id | 99 | channelIdServer1 = res.body.videoChannels[0].id |
92 | } | 100 | } |
93 | 101 | ||
94 | { | 102 | { |
95 | const res = await getMyUserInformation(servers[1].url, servers[1].accessToken) | 103 | const res = await getMyUserInformation(servers[1].url, servers[1].accessToken) |
96 | channelIdServer2 = res.body.videoChannels[ 0 ].id | 104 | channelIdServer2 = res.body.videoChannels[0].id |
97 | } | 105 | } |
98 | 106 | ||
99 | await doubleFollow(servers[0], servers[1]) | 107 | await doubleFollow(servers[0], servers[1]) |
@@ -111,6 +119,48 @@ describe('Test video imports', function () { | |||
111 | const attributes = immutableAssign(baseAttributes, { targetUrl: getYoutubeVideoUrl() }) | 119 | const attributes = immutableAssign(baseAttributes, { targetUrl: getYoutubeVideoUrl() }) |
112 | const res = await importVideo(servers[0].url, servers[0].accessToken, attributes) | 120 | const res = await importVideo(servers[0].url, servers[0].accessToken, attributes) |
113 | expect(res.body.video.name).to.equal('small video - youtube') | 121 | expect(res.body.video.name).to.equal('small video - youtube') |
122 | expect(res.body.video.thumbnailPath).to.equal(`/static/thumbnails/${res.body.video.uuid}.jpg`) | ||
123 | expect(res.body.video.previewPath).to.equal(`/static/previews/${res.body.video.uuid}.jpg`) | ||
124 | await testImage(servers[0].url, 'video_import_thumbnail', res.body.video.thumbnailPath) | ||
125 | await testImage(servers[0].url, 'video_import_preview', res.body.video.previewPath) | ||
126 | |||
127 | const resCaptions = await listVideoCaptions(servers[0].url, res.body.video.id) | ||
128 | const videoCaptions: VideoCaption[] = resCaptions.body.data | ||
129 | expect(videoCaptions).to.have.lengthOf(2) | ||
130 | |||
131 | const enCaption = videoCaptions.find(caption => caption.language.id === 'en') | ||
132 | expect(enCaption).to.exist | ||
133 | expect(enCaption.language.label).to.equal('English') | ||
134 | expect(enCaption.captionPath).to.equal(`/static/video-captions/${res.body.video.uuid}-en.vtt`) | ||
135 | await testCaptionFile(servers[0].url, enCaption.captionPath, `WEBVTT | ||
136 | Kind: captions | ||
137 | Language: en | ||
138 | |||
139 | 00:00:01.600 --> 00:00:04.200 | ||
140 | English (US) | ||
141 | |||
142 | 00:00:05.900 --> 00:00:07.999 | ||
143 | This is a subtitle in American English | ||
144 | |||
145 | 00:00:10.000 --> 00:00:14.000 | ||
146 | Adding subtitles is very easy to do`) | ||
147 | |||
148 | const frCaption = videoCaptions.find(caption => caption.language.id === 'fr') | ||
149 | expect(frCaption).to.exist | ||
150 | expect(frCaption.language.label).to.equal('French') | ||
151 | expect(frCaption.captionPath).to.equal(`/static/video-captions/${res.body.video.uuid}-fr.vtt`) | ||
152 | await testCaptionFile(servers[0].url, frCaption.captionPath, `WEBVTT | ||
153 | Kind: captions | ||
154 | Language: fr | ||
155 | |||
156 | 00:00:01.600 --> 00:00:04.200 | ||
157 | Français (FR) | ||
158 | |||
159 | 00:00:05.900 --> 00:00:07.999 | ||
160 | C'est un sous-titre français | ||
161 | |||
162 | 00:00:10.000 --> 00:00:14.000 | ||
163 | Ajouter un sous-titre est vraiment facile`) | ||
114 | } | 164 | } |
115 | 165 | ||
116 | { | 166 | { |
@@ -214,7 +264,7 @@ describe('Test video imports', function () { | |||
214 | 264 | ||
215 | await checkVideoServer2(server.url, res.body.data[0].uuid) | 265 | await checkVideoServer2(server.url, res.body.data[0].uuid) |
216 | 266 | ||
217 | const [ ,videoHttp, videoMagnet, videoTorrent ] = res.body.data | 267 | const [ , videoHttp, videoMagnet, videoTorrent ] = res.body.data |
218 | await checkVideosServer1(server.url, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid) | 268 | await checkVideosServer1(server.url, videoHttp.uuid, videoMagnet.uuid, videoTorrent.uuid) |
219 | } | 269 | } |
220 | }) | 270 | }) |
diff --git a/server/tests/api/videos/video-nsfw.ts b/server/tests/api/videos/video-nsfw.ts index ad6a4b43f..b16b484b9 100644 --- a/server/tests/api/videos/video-nsfw.ts +++ b/server/tests/api/videos/video-nsfw.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -19,12 +19,20 @@ import { | |||
19 | updateCustomConfig, | 19 | updateCustomConfig, |
20 | updateMyUser | 20 | updateMyUser |
21 | } from '../../../../shared/extra-utils' | 21 | } from '../../../../shared/extra-utils' |
22 | import { ServerConfig } from '../../../../shared/models' | 22 | import { ServerConfig, VideosOverview } from '../../../../shared/models' |
23 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' | 23 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' |
24 | import { User } from '../../../../shared/models/users' | 24 | import { User } from '../../../../shared/models/users' |
25 | import { getVideosOverview, getVideosOverviewWithToken } from '@shared/extra-utils/overviews/overviews' | ||
25 | 26 | ||
26 | const expect = chai.expect | 27 | const expect = chai.expect |
27 | 28 | ||
29 | function createOverviewRes (res: any) { | ||
30 | const overview = res.body as VideosOverview | ||
31 | |||
32 | const videos = overview.categories[0].videos | ||
33 | return { body: { data: videos, total: videos.length } } | ||
34 | } | ||
35 | |||
28 | describe('Test video NSFW policy', function () { | 36 | describe('Test video NSFW policy', function () { |
29 | let server: ServerInfo | 37 | let server: ServerInfo |
30 | let userAccessToken: string | 38 | let userAccessToken: string |
@@ -36,22 +44,38 @@ describe('Test video NSFW policy', function () { | |||
36 | const user: User = res.body | 44 | const user: User = res.body |
37 | const videoChannelName = user.videoChannels[0].name | 45 | const videoChannelName = user.videoChannels[0].name |
38 | const accountName = user.account.name + '@' + user.account.host | 46 | const accountName = user.account.name + '@' + user.account.host |
47 | const hasQuery = Object.keys(query).length !== 0 | ||
48 | let promises: Promise<any>[] | ||
39 | 49 | ||
40 | if (token) { | 50 | if (token) { |
41 | return Promise.all([ | 51 | promises = [ |
42 | getVideosListWithToken(server.url, token, query), | 52 | getVideosListWithToken(server.url, token, query), |
43 | searchVideoWithToken(server.url, 'n', token, query), | 53 | searchVideoWithToken(server.url, 'n', token, query), |
44 | getAccountVideos(server.url, token, accountName, 0, 5, undefined, query), | 54 | getAccountVideos(server.url, token, accountName, 0, 5, undefined, query), |
45 | getVideoChannelVideos(server.url, token, videoChannelName, 0, 5, undefined, query) | 55 | getVideoChannelVideos(server.url, token, videoChannelName, 0, 5, undefined, query) |
46 | ]) | 56 | ] |
57 | |||
58 | // Overviews do not support video filters | ||
59 | if (!hasQuery) { | ||
60 | promises.push(getVideosOverviewWithToken(server.url, 1, token).then(res => createOverviewRes(res))) | ||
61 | } | ||
62 | |||
63 | return Promise.all(promises) | ||
47 | } | 64 | } |
48 | 65 | ||
49 | return Promise.all([ | 66 | promises = [ |
50 | getVideosList(server.url), | 67 | getVideosList(server.url), |
51 | searchVideo(server.url, 'n'), | 68 | searchVideo(server.url, 'n'), |
52 | getAccountVideos(server.url, undefined, accountName, 0, 5), | 69 | getAccountVideos(server.url, undefined, accountName, 0, 5), |
53 | getVideoChannelVideos(server.url, undefined, videoChannelName, 0, 5) | 70 | getVideoChannelVideos(server.url, undefined, videoChannelName, 0, 5) |
54 | ]) | 71 | ] |
72 | |||
73 | // Overviews do not support video filters | ||
74 | if (!hasQuery) { | ||
75 | promises.push(getVideosOverview(server.url, 1).then(res => createOverviewRes(res))) | ||
76 | } | ||
77 | |||
78 | return Promise.all(promises) | ||
55 | }) | 79 | }) |
56 | } | 80 | } |
57 | 81 | ||
@@ -63,12 +87,12 @@ describe('Test video NSFW policy', function () { | |||
63 | await setAccessTokensToServers([ server ]) | 87 | await setAccessTokensToServers([ server ]) |
64 | 88 | ||
65 | { | 89 | { |
66 | const attributes = { name: 'nsfw', nsfw: true } | 90 | const attributes = { name: 'nsfw', nsfw: true, category: 1 } |
67 | await uploadVideo(server.url, server.accessToken, attributes) | 91 | await uploadVideo(server.url, server.accessToken, attributes) |
68 | } | 92 | } |
69 | 93 | ||
70 | { | 94 | { |
71 | const attributes = { name: 'normal', nsfw: false } | 95 | const attributes = { name: 'normal', nsfw: false, category: 1 } |
72 | await uploadVideo(server.url, server.accessToken, attributes) | 96 | await uploadVideo(server.url, server.accessToken, attributes) |
73 | } | 97 | } |
74 | 98 | ||
@@ -89,8 +113,8 @@ describe('Test video NSFW policy', function () { | |||
89 | 113 | ||
90 | const videos = res.body.data | 114 | const videos = res.body.data |
91 | expect(videos).to.have.lengthOf(2) | 115 | expect(videos).to.have.lengthOf(2) |
92 | expect(videos[ 0 ].name).to.equal('normal') | 116 | expect(videos[0].name).to.equal('normal') |
93 | expect(videos[ 1 ].name).to.equal('nsfw') | 117 | expect(videos[1].name).to.equal('nsfw') |
94 | } | 118 | } |
95 | }) | 119 | }) |
96 | 120 | ||
@@ -107,7 +131,7 @@ describe('Test video NSFW policy', function () { | |||
107 | 131 | ||
108 | const videos = res.body.data | 132 | const videos = res.body.data |
109 | expect(videos).to.have.lengthOf(1) | 133 | expect(videos).to.have.lengthOf(1) |
110 | expect(videos[ 0 ].name).to.equal('normal') | 134 | expect(videos[0].name).to.equal('normal') |
111 | } | 135 | } |
112 | }) | 136 | }) |
113 | 137 | ||
@@ -124,8 +148,8 @@ describe('Test video NSFW policy', function () { | |||
124 | 148 | ||
125 | const videos = res.body.data | 149 | const videos = res.body.data |
126 | expect(videos).to.have.lengthOf(2) | 150 | expect(videos).to.have.lengthOf(2) |
127 | expect(videos[ 0 ].name).to.equal('normal') | 151 | expect(videos[0].name).to.equal('normal') |
128 | expect(videos[ 1 ].name).to.equal('nsfw') | 152 | expect(videos[1].name).to.equal('nsfw') |
129 | } | 153 | } |
130 | }) | 154 | }) |
131 | }) | 155 | }) |
@@ -154,8 +178,8 @@ describe('Test video NSFW policy', function () { | |||
154 | 178 | ||
155 | const videos = res.body.data | 179 | const videos = res.body.data |
156 | expect(videos).to.have.lengthOf(2) | 180 | expect(videos).to.have.lengthOf(2) |
157 | expect(videos[ 0 ].name).to.equal('normal') | 181 | expect(videos[0].name).to.equal('normal') |
158 | expect(videos[ 1 ].name).to.equal('nsfw') | 182 | expect(videos[1].name).to.equal('nsfw') |
159 | } | 183 | } |
160 | }) | 184 | }) |
161 | 185 | ||
@@ -171,8 +195,8 @@ describe('Test video NSFW policy', function () { | |||
171 | 195 | ||
172 | const videos = res.body.data | 196 | const videos = res.body.data |
173 | expect(videos).to.have.lengthOf(2) | 197 | expect(videos).to.have.lengthOf(2) |
174 | expect(videos[ 0 ].name).to.equal('normal') | 198 | expect(videos[0].name).to.equal('normal') |
175 | expect(videos[ 1 ].name).to.equal('nsfw') | 199 | expect(videos[1].name).to.equal('nsfw') |
176 | } | 200 | } |
177 | }) | 201 | }) |
178 | 202 | ||
@@ -188,7 +212,7 @@ describe('Test video NSFW policy', function () { | |||
188 | 212 | ||
189 | const videos = res.body.data | 213 | const videos = res.body.data |
190 | expect(videos).to.have.lengthOf(1) | 214 | expect(videos).to.have.lengthOf(1) |
191 | expect(videos[ 0 ].name).to.equal('normal') | 215 | expect(videos[0].name).to.equal('normal') |
192 | } | 216 | } |
193 | }) | 217 | }) |
194 | 218 | ||
@@ -198,8 +222,8 @@ describe('Test video NSFW policy', function () { | |||
198 | 222 | ||
199 | const videos = res.body.data | 223 | const videos = res.body.data |
200 | expect(videos).to.have.lengthOf(2) | 224 | expect(videos).to.have.lengthOf(2) |
201 | expect(videos[ 0 ].name).to.equal('normal') | 225 | expect(videos[0].name).to.equal('normal') |
202 | expect(videos[ 1 ].name).to.equal('nsfw') | 226 | expect(videos[1].name).to.equal('nsfw') |
203 | }) | 227 | }) |
204 | 228 | ||
205 | it('Should display NSFW videos when the nsfw param === true', async function () { | 229 | it('Should display NSFW videos when the nsfw param === true', async function () { |
@@ -208,7 +232,7 @@ describe('Test video NSFW policy', function () { | |||
208 | 232 | ||
209 | const videos = res.body.data | 233 | const videos = res.body.data |
210 | expect(videos).to.have.lengthOf(1) | 234 | expect(videos).to.have.lengthOf(1) |
211 | expect(videos[ 0 ].name).to.equal('nsfw') | 235 | expect(videos[0].name).to.equal('nsfw') |
212 | } | 236 | } |
213 | }) | 237 | }) |
214 | 238 | ||
@@ -218,7 +242,7 @@ describe('Test video NSFW policy', function () { | |||
218 | 242 | ||
219 | const videos = res.body.data | 243 | const videos = res.body.data |
220 | expect(videos).to.have.lengthOf(1) | 244 | expect(videos).to.have.lengthOf(1) |
221 | expect(videos[ 0 ].name).to.equal('normal') | 245 | expect(videos[0].name).to.equal('normal') |
222 | } | 246 | } |
223 | }) | 247 | }) |
224 | 248 | ||
@@ -228,8 +252,8 @@ describe('Test video NSFW policy', function () { | |||
228 | 252 | ||
229 | const videos = res.body.data | 253 | const videos = res.body.data |
230 | expect(videos).to.have.lengthOf(2) | 254 | expect(videos).to.have.lengthOf(2) |
231 | expect(videos[ 0 ].name).to.equal('normal') | 255 | expect(videos[0].name).to.equal('normal') |
232 | expect(videos[ 1 ].name).to.equal('nsfw') | 256 | expect(videos[1].name).to.equal('nsfw') |
233 | } | 257 | } |
234 | }) | 258 | }) |
235 | }) | 259 | }) |
diff --git a/server/tests/api/videos/video-playlist-thumbnails.ts b/server/tests/api/videos/video-playlist-thumbnails.ts index 73ab02c17..a93a0b7de 100644 --- a/server/tests/api/videos/video-playlist-thumbnails.ts +++ b/server/tests/api/videos/video-playlist-thumbnails.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -8,14 +8,15 @@ import { | |||
8 | createVideoPlaylist, | 8 | createVideoPlaylist, |
9 | doubleFollow, | 9 | doubleFollow, |
10 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
11 | getVideoPlaylistsList, removeVideoFromPlaylist, | 11 | getVideoPlaylistsList, |
12 | removeVideoFromPlaylist, | ||
13 | reorderVideosPlaylist, | ||
12 | ServerInfo, | 14 | ServerInfo, |
13 | setAccessTokensToServers, | 15 | setAccessTokensToServers, |
14 | setDefaultVideoChannel, | 16 | setDefaultVideoChannel, |
15 | testImage, | 17 | testImage, |
16 | uploadVideoAndGetId, | 18 | uploadVideoAndGetId, |
17 | waitJobs, | 19 | waitJobs |
18 | reorderVideosPlaylist | ||
19 | } from '../../../../shared/extra-utils' | 20 | } from '../../../../shared/extra-utils' |
20 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 21 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
21 | 22 | ||
@@ -69,19 +70,19 @@ describe('Playlist thumbnail', function () { | |||
69 | this.timeout(30000) | 70 | this.timeout(30000) |
70 | 71 | ||
71 | const res = await createVideoPlaylist({ | 72 | const res = await createVideoPlaylist({ |
72 | url: servers[ 1 ].url, | 73 | url: servers[1].url, |
73 | token: servers[ 1 ].accessToken, | 74 | token: servers[1].accessToken, |
74 | playlistAttrs: { | 75 | playlistAttrs: { |
75 | displayName: 'playlist without thumbnail', | 76 | displayName: 'playlist without thumbnail', |
76 | privacy: VideoPlaylistPrivacy.PUBLIC, | 77 | privacy: VideoPlaylistPrivacy.PUBLIC, |
77 | videoChannelId: servers[ 1 ].videoChannel.id | 78 | videoChannelId: servers[1].videoChannel.id |
78 | } | 79 | } |
79 | }) | 80 | }) |
80 | playlistWithoutThumbnail = res.body.videoPlaylist.id | 81 | playlistWithoutThumbnail = res.body.videoPlaylist.id |
81 | 82 | ||
82 | const res2 = await addVideoInPlaylist({ | 83 | const res2 = await addVideoInPlaylist({ |
83 | url: servers[ 1 ].url, | 84 | url: servers[1].url, |
84 | token: servers[ 1 ].accessToken, | 85 | token: servers[1].accessToken, |
85 | playlistId: playlistWithoutThumbnail, | 86 | playlistId: playlistWithoutThumbnail, |
86 | elementAttrs: { videoId: video1 } | 87 | elementAttrs: { videoId: video1 } |
87 | }) | 88 | }) |
@@ -99,20 +100,20 @@ describe('Playlist thumbnail', function () { | |||
99 | this.timeout(30000) | 100 | this.timeout(30000) |
100 | 101 | ||
101 | const res = await createVideoPlaylist({ | 102 | const res = await createVideoPlaylist({ |
102 | url: servers[ 1 ].url, | 103 | url: servers[1].url, |
103 | token: servers[ 1 ].accessToken, | 104 | token: servers[1].accessToken, |
104 | playlistAttrs: { | 105 | playlistAttrs: { |
105 | displayName: 'playlist with thumbnail', | 106 | displayName: 'playlist with thumbnail', |
106 | privacy: VideoPlaylistPrivacy.PUBLIC, | 107 | privacy: VideoPlaylistPrivacy.PUBLIC, |
107 | videoChannelId: servers[ 1 ].videoChannel.id, | 108 | videoChannelId: servers[1].videoChannel.id, |
108 | thumbnailfile: 'thumbnail.jpg' | 109 | thumbnailfile: 'thumbnail.jpg' |
109 | } | 110 | } |
110 | }) | 111 | }) |
111 | playlistWithThumbnail = res.body.videoPlaylist.id | 112 | playlistWithThumbnail = res.body.videoPlaylist.id |
112 | 113 | ||
113 | const res2 = await addVideoInPlaylist({ | 114 | const res2 = await addVideoInPlaylist({ |
114 | url: servers[ 1 ].url, | 115 | url: servers[1].url, |
115 | token: servers[ 1 ].accessToken, | 116 | token: servers[1].accessToken, |
116 | playlistId: playlistWithThumbnail, | 117 | playlistId: playlistWithThumbnail, |
117 | elementAttrs: { videoId: video1 } | 118 | elementAttrs: { videoId: video1 } |
118 | }) | 119 | }) |
@@ -130,8 +131,8 @@ describe('Playlist thumbnail', function () { | |||
130 | this.timeout(30000) | 131 | this.timeout(30000) |
131 | 132 | ||
132 | const res = await addVideoInPlaylist({ | 133 | const res = await addVideoInPlaylist({ |
133 | url: servers[ 1 ].url, | 134 | url: servers[1].url, |
134 | token: servers[ 1 ].accessToken, | 135 | token: servers[1].accessToken, |
135 | playlistId: playlistWithoutThumbnail, | 136 | playlistId: playlistWithoutThumbnail, |
136 | elementAttrs: { videoId: video2 } | 137 | elementAttrs: { videoId: video2 } |
137 | }) | 138 | }) |
@@ -159,8 +160,8 @@ describe('Playlist thumbnail', function () { | |||
159 | this.timeout(30000) | 160 | this.timeout(30000) |
160 | 161 | ||
161 | const res = await addVideoInPlaylist({ | 162 | const res = await addVideoInPlaylist({ |
162 | url: servers[ 1 ].url, | 163 | url: servers[1].url, |
163 | token: servers[ 1 ].accessToken, | 164 | token: servers[1].accessToken, |
164 | playlistId: playlistWithThumbnail, | 165 | playlistId: playlistWithThumbnail, |
165 | elementAttrs: { videoId: video2 } | 166 | elementAttrs: { videoId: video2 } |
166 | }) | 167 | }) |
@@ -188,8 +189,8 @@ describe('Playlist thumbnail', function () { | |||
188 | this.timeout(30000) | 189 | this.timeout(30000) |
189 | 190 | ||
190 | await removeVideoFromPlaylist({ | 191 | await removeVideoFromPlaylist({ |
191 | url: servers[ 1 ].url, | 192 | url: servers[1].url, |
192 | token: servers[ 1 ].accessToken, | 193 | token: servers[1].accessToken, |
193 | playlistId: playlistWithoutThumbnail, | 194 | playlistId: playlistWithoutThumbnail, |
194 | playlistElementId: withoutThumbnailE1 | 195 | playlistElementId: withoutThumbnailE1 |
195 | }) | 196 | }) |
@@ -206,8 +207,8 @@ describe('Playlist thumbnail', function () { | |||
206 | this.timeout(30000) | 207 | this.timeout(30000) |
207 | 208 | ||
208 | await removeVideoFromPlaylist({ | 209 | await removeVideoFromPlaylist({ |
209 | url: servers[ 1 ].url, | 210 | url: servers[1].url, |
210 | token: servers[ 1 ].accessToken, | 211 | token: servers[1].accessToken, |
211 | playlistId: playlistWithThumbnail, | 212 | playlistId: playlistWithThumbnail, |
212 | playlistElementId: withThumbnailE1 | 213 | playlistElementId: withThumbnailE1 |
213 | }) | 214 | }) |
@@ -224,8 +225,8 @@ describe('Playlist thumbnail', function () { | |||
224 | this.timeout(30000) | 225 | this.timeout(30000) |
225 | 226 | ||
226 | await removeVideoFromPlaylist({ | 227 | await removeVideoFromPlaylist({ |
227 | url: servers[ 1 ].url, | 228 | url: servers[1].url, |
228 | token: servers[ 1 ].accessToken, | 229 | token: servers[1].accessToken, |
229 | playlistId: playlistWithoutThumbnail, | 230 | playlistId: playlistWithoutThumbnail, |
230 | playlistElementId: withoutThumbnailE2 | 231 | playlistElementId: withoutThumbnailE2 |
231 | }) | 232 | }) |
@@ -242,8 +243,8 @@ describe('Playlist thumbnail', function () { | |||
242 | this.timeout(30000) | 243 | this.timeout(30000) |
243 | 244 | ||
244 | await removeVideoFromPlaylist({ | 245 | await removeVideoFromPlaylist({ |
245 | url: servers[ 1 ].url, | 246 | url: servers[1].url, |
246 | token: servers[ 1 ].accessToken, | 247 | token: servers[1].accessToken, |
247 | playlistId: playlistWithThumbnail, | 248 | playlistId: playlistWithThumbnail, |
248 | playlistElementId: withThumbnailE2 | 249 | playlistElementId: withThumbnailE2 |
249 | }) | 250 | }) |
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts index 9fd48ac7c..2bb97d7a8 100644 --- a/server/tests/api/videos/video-playlists.ts +++ b/server/tests/api/videos/video-playlists.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -141,12 +141,12 @@ describe('Test video playlists', function () { | |||
141 | servers[2].videos = await Promise.all(serverPromises[2]) | 141 | servers[2].videos = await Promise.all(serverPromises[2]) |
142 | } | 142 | } |
143 | 143 | ||
144 | 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 |
145 | 145 | ||
146 | { | 146 | { |
147 | await createUser({ | 147 | await createUser({ |
148 | url: servers[ 0 ].url, | 148 | url: servers[0].url, |
149 | accessToken: servers[ 0 ].accessToken, | 149 | accessToken: servers[0].accessToken, |
150 | username: 'user1', | 150 | username: 'user1', |
151 | password: 'password' | 151 | password: 'password' |
152 | }) | 152 | }) |
@@ -158,17 +158,17 @@ describe('Test video playlists', function () { | |||
158 | 158 | ||
159 | describe('Get default playlists', function () { | 159 | describe('Get default playlists', function () { |
160 | it('Should list video playlist privacies', async function () { | 160 | it('Should list video playlist privacies', async function () { |
161 | const res = await getVideoPlaylistPrivacies(servers[ 0 ].url) | 161 | const res = await getVideoPlaylistPrivacies(servers[0].url) |
162 | 162 | ||
163 | const privacies = res.body | 163 | const privacies = res.body |
164 | expect(Object.keys(privacies)).to.have.length.at.least(3) | 164 | expect(Object.keys(privacies)).to.have.length.at.least(3) |
165 | 165 | ||
166 | expect(privacies[ 3 ]).to.equal('Private') | 166 | expect(privacies[3]).to.equal('Private') |
167 | }) | 167 | }) |
168 | 168 | ||
169 | it('Should list watch later playlist', async function () { | 169 | it('Should list watch later playlist', async function () { |
170 | const url = servers[ 0 ].url | 170 | const url = servers[0].url |
171 | const accessToken = servers[ 0 ].accessToken | 171 | const accessToken = servers[0].accessToken |
172 | 172 | ||
173 | { | 173 | { |
174 | 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) |
@@ -176,7 +176,7 @@ describe('Test video playlists', function () { | |||
176 | expect(res.body.total).to.equal(1) | 176 | expect(res.body.total).to.equal(1) |
177 | expect(res.body.data).to.have.lengthOf(1) | 177 | expect(res.body.data).to.have.lengthOf(1) |
178 | 178 | ||
179 | const playlist: VideoPlaylist = res.body.data[ 0 ] | 179 | const playlist: VideoPlaylist = res.body.data[0] |
180 | expect(playlist.displayName).to.equal('Watch later') | 180 | expect(playlist.displayName).to.equal('Watch later') |
181 | expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER) | 181 | expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER) |
182 | expect(playlist.type.label).to.equal('Watch later') | 182 | expect(playlist.type.label).to.equal('Watch later') |
@@ -197,15 +197,15 @@ describe('Test video playlists', function () { | |||
197 | }) | 197 | }) |
198 | 198 | ||
199 | it('Should get private playlist for a classic user', async function () { | 199 | it('Should get private playlist for a classic user', async function () { |
200 | const token = await generateUserAccessToken(servers[ 0 ], 'toto') | 200 | const token = await generateUserAccessToken(servers[0], 'toto') |
201 | 201 | ||
202 | const res = await getAccountPlaylistsListWithToken(servers[ 0 ].url, token, 'toto', 0, 5) | 202 | const res = await getAccountPlaylistsListWithToken(servers[0].url, token, 'toto', 0, 5) |
203 | 203 | ||
204 | expect(res.body.total).to.equal(1) | 204 | expect(res.body.total).to.equal(1) |
205 | expect(res.body.data).to.have.lengthOf(1) | 205 | expect(res.body.data).to.have.lengthOf(1) |
206 | 206 | ||
207 | const playlistId = res.body.data[ 0 ].id | 207 | const playlistId = res.body.data[0].id |
208 | await getPlaylistVideos(servers[ 0 ].url, token, playlistId, 0, 5) | 208 | await getPlaylistVideos(servers[0].url, token, playlistId, 0, 5) |
209 | }) | 209 | }) |
210 | }) | 210 | }) |
211 | 211 | ||
@@ -215,14 +215,14 @@ describe('Test video playlists', function () { | |||
215 | this.timeout(30000) | 215 | this.timeout(30000) |
216 | 216 | ||
217 | await createVideoPlaylist({ | 217 | await createVideoPlaylist({ |
218 | url: servers[ 0 ].url, | 218 | url: servers[0].url, |
219 | token: servers[ 0 ].accessToken, | 219 | token: servers[0].accessToken, |
220 | playlistAttrs: { | 220 | playlistAttrs: { |
221 | displayName: 'my super playlist', | 221 | displayName: 'my super playlist', |
222 | privacy: VideoPlaylistPrivacy.PUBLIC, | 222 | privacy: VideoPlaylistPrivacy.PUBLIC, |
223 | description: 'my super description', | 223 | description: 'my super description', |
224 | thumbnailfile: 'thumbnail.jpg', | 224 | thumbnailfile: 'thumbnail.jpg', |
225 | videoChannelId: servers[ 0 ].videoChannel.id | 225 | videoChannelId: servers[0].videoChannel.id |
226 | } | 226 | } |
227 | }) | 227 | }) |
228 | 228 | ||
@@ -233,7 +233,7 @@ describe('Test video playlists', function () { | |||
233 | expect(res.body.total).to.equal(1) | 233 | expect(res.body.total).to.equal(1) |
234 | expect(res.body.data).to.have.lengthOf(1) | 234 | expect(res.body.data).to.have.lengthOf(1) |
235 | 235 | ||
236 | const playlistFromList = res.body.data[ 0 ] as VideoPlaylist | 236 | const playlistFromList = res.body.data[0] as VideoPlaylist |
237 | 237 | ||
238 | const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) | 238 | const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) |
239 | const playlistFromGet = res2.body | 239 | const playlistFromGet = res2.body |
@@ -266,12 +266,12 @@ describe('Test video playlists', function () { | |||
266 | 266 | ||
267 | { | 267 | { |
268 | const res = await createVideoPlaylist({ | 268 | const res = await createVideoPlaylist({ |
269 | url: servers[ 1 ].url, | 269 | url: servers[1].url, |
270 | token: servers[ 1 ].accessToken, | 270 | token: servers[1].accessToken, |
271 | playlistAttrs: { | 271 | playlistAttrs: { |
272 | displayName: 'playlist 2', | 272 | displayName: 'playlist 2', |
273 | privacy: VideoPlaylistPrivacy.PUBLIC, | 273 | privacy: VideoPlaylistPrivacy.PUBLIC, |
274 | videoChannelId: servers[ 1 ].videoChannel.id | 274 | videoChannelId: servers[1].videoChannel.id |
275 | } | 275 | } |
276 | }) | 276 | }) |
277 | playlistServer2Id1 = res.body.videoPlaylist.id | 277 | playlistServer2Id1 = res.body.videoPlaylist.id |
@@ -279,13 +279,13 @@ describe('Test video playlists', function () { | |||
279 | 279 | ||
280 | { | 280 | { |
281 | const res = await createVideoPlaylist({ | 281 | const res = await createVideoPlaylist({ |
282 | url: servers[ 1 ].url, | 282 | url: servers[1].url, |
283 | token: servers[ 1 ].accessToken, | 283 | token: servers[1].accessToken, |
284 | playlistAttrs: { | 284 | playlistAttrs: { |
285 | displayName: 'playlist 3', | 285 | displayName: 'playlist 3', |
286 | privacy: VideoPlaylistPrivacy.PUBLIC, | 286 | privacy: VideoPlaylistPrivacy.PUBLIC, |
287 | thumbnailfile: 'thumbnail.jpg', | 287 | thumbnailfile: 'thumbnail.jpg', |
288 | videoChannelId: servers[ 1 ].videoChannel.id | 288 | videoChannelId: servers[1].videoChannel.id |
289 | } | 289 | } |
290 | }) | 290 | }) |
291 | 291 | ||
@@ -293,24 +293,24 @@ describe('Test video playlists', function () { | |||
293 | playlistServer2UUID2 = res.body.videoPlaylist.uuid | 293 | playlistServer2UUID2 = res.body.videoPlaylist.uuid |
294 | } | 294 | } |
295 | 295 | ||
296 | for (let id of [ playlistServer2Id1, playlistServer2Id2 ]) { | 296 | for (const id of [ playlistServer2Id1, playlistServer2Id2 ]) { |
297 | await addVideoInPlaylist({ | 297 | await addVideoInPlaylist({ |
298 | url: servers[ 1 ].url, | 298 | url: servers[1].url, |
299 | token: servers[ 1 ].accessToken, | 299 | token: servers[1].accessToken, |
300 | playlistId: id, | 300 | playlistId: id, |
301 | elementAttrs: { videoId: servers[ 1 ].videos[ 0 ].id, startTimestamp: 1, stopTimestamp: 2 } | 301 | elementAttrs: { videoId: servers[1].videos[0].id, startTimestamp: 1, stopTimestamp: 2 } |
302 | }) | 302 | }) |
303 | await addVideoInPlaylist({ | 303 | await addVideoInPlaylist({ |
304 | url: servers[ 1 ].url, | 304 | url: servers[1].url, |
305 | token: servers[ 1 ].accessToken, | 305 | token: servers[1].accessToken, |
306 | playlistId: id, | 306 | playlistId: id, |
307 | elementAttrs: { videoId: servers[ 1 ].videos[ 1 ].id } | 307 | elementAttrs: { videoId: servers[1].videos[1].id } |
308 | }) | 308 | }) |
309 | } | 309 | } |
310 | 310 | ||
311 | await waitJobs(servers) | 311 | await waitJobs(servers) |
312 | 312 | ||
313 | for (const server of [ servers[ 0 ], servers[ 1 ] ]) { | 313 | for (const server of [ servers[0], servers[1] ]) { |
314 | const res = await getVideoPlaylistsList(server.url, 0, 5) | 314 | const res = await getVideoPlaylistsList(server.url, 0, 5) |
315 | 315 | ||
316 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') | 316 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') |
@@ -322,7 +322,7 @@ describe('Test video playlists', function () { | |||
322 | await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) | 322 | await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) |
323 | } | 323 | } |
324 | 324 | ||
325 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | 325 | const res = await getVideoPlaylistsList(servers[2].url, 0, 5) |
326 | expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined | 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 | 327 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined |
328 | }) | 328 | }) |
@@ -331,13 +331,13 @@ describe('Test video playlists', function () { | |||
331 | this.timeout(30000) | 331 | this.timeout(30000) |
332 | 332 | ||
333 | // Server 2 and server 3 follow each other | 333 | // Server 2 and server 3 follow each other |
334 | await doubleFollow(servers[ 1 ], servers[ 2 ]) | 334 | await doubleFollow(servers[1], servers[2]) |
335 | 335 | ||
336 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | 336 | const res = await getVideoPlaylistsList(servers[2].url, 0, 5) |
337 | 337 | ||
338 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') | 338 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') |
339 | expect(playlist2).to.not.be.undefined | 339 | expect(playlist2).to.not.be.undefined |
340 | await testImage(servers[ 2 ].url, 'thumbnail-playlist', playlist2.thumbnailPath) | 340 | await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath) |
341 | 341 | ||
342 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined | 342 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined |
343 | }) | 343 | }) |
@@ -349,25 +349,25 @@ describe('Test video playlists', function () { | |||
349 | this.timeout(30000) | 349 | this.timeout(30000) |
350 | 350 | ||
351 | { | 351 | { |
352 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, 'createdAt') | 352 | const res = await getVideoPlaylistsList(servers[2].url, 1, 2, 'createdAt') |
353 | 353 | ||
354 | expect(res.body.total).to.equal(3) | 354 | expect(res.body.total).to.equal(3) |
355 | 355 | ||
356 | const data: VideoPlaylist[] = res.body.data | 356 | const data: VideoPlaylist[] = res.body.data |
357 | expect(data).to.have.lengthOf(2) | 357 | expect(data).to.have.lengthOf(2) |
358 | expect(data[ 0 ].displayName).to.equal('playlist 2') | 358 | expect(data[0].displayName).to.equal('playlist 2') |
359 | expect(data[ 1 ].displayName).to.equal('playlist 3') | 359 | expect(data[1].displayName).to.equal('playlist 3') |
360 | } | 360 | } |
361 | 361 | ||
362 | { | 362 | { |
363 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, '-createdAt') | 363 | const res = await getVideoPlaylistsList(servers[2].url, 1, 2, '-createdAt') |
364 | 364 | ||
365 | expect(res.body.total).to.equal(3) | 365 | expect(res.body.total).to.equal(3) |
366 | 366 | ||
367 | const data: VideoPlaylist[] = res.body.data | 367 | const data: VideoPlaylist[] = res.body.data |
368 | expect(data).to.have.lengthOf(2) | 368 | expect(data).to.have.lengthOf(2) |
369 | expect(data[ 0 ].displayName).to.equal('playlist 2') | 369 | expect(data[0].displayName).to.equal('playlist 2') |
370 | expect(data[ 1 ].displayName).to.equal('my super playlist') | 370 | expect(data[1].displayName).to.equal('my super playlist') |
371 | } | 371 | } |
372 | }) | 372 | }) |
373 | 373 | ||
@@ -375,13 +375,13 @@ describe('Test video playlists', function () { | |||
375 | this.timeout(30000) | 375 | this.timeout(30000) |
376 | 376 | ||
377 | { | 377 | { |
378 | const res = await getVideoChannelPlaylistsList(servers[ 0 ].url, 'root_channel', 0, 2, '-createdAt') | 378 | const res = await getVideoChannelPlaylistsList(servers[0].url, 'root_channel', 0, 2, '-createdAt') |
379 | 379 | ||
380 | expect(res.body.total).to.equal(1) | 380 | expect(res.body.total).to.equal(1) |
381 | 381 | ||
382 | const data: VideoPlaylist[] = res.body.data | 382 | const data: VideoPlaylist[] = res.body.data |
383 | expect(data).to.have.lengthOf(1) | 383 | expect(data).to.have.lengthOf(1) |
384 | expect(data[ 0 ].displayName).to.equal('my super playlist') | 384 | expect(data[0].displayName).to.equal('my super playlist') |
385 | } | 385 | } |
386 | }) | 386 | }) |
387 | 387 | ||
@@ -389,37 +389,37 @@ describe('Test video playlists', function () { | |||
389 | this.timeout(30000) | 389 | this.timeout(30000) |
390 | 390 | ||
391 | { | 391 | { |
392 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, '-createdAt') | 392 | const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, '-createdAt') |
393 | 393 | ||
394 | expect(res.body.total).to.equal(2) | 394 | expect(res.body.total).to.equal(2) |
395 | 395 | ||
396 | const data: VideoPlaylist[] = res.body.data | 396 | const data: VideoPlaylist[] = res.body.data |
397 | expect(data).to.have.lengthOf(1) | 397 | expect(data).to.have.lengthOf(1) |
398 | expect(data[ 0 ].displayName).to.equal('playlist 2') | 398 | expect(data[0].displayName).to.equal('playlist 2') |
399 | } | 399 | } |
400 | 400 | ||
401 | { | 401 | { |
402 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, 'createdAt') | 402 | const res = await getAccountPlaylistsList(servers[1].url, 'root', 1, 2, 'createdAt') |
403 | 403 | ||
404 | expect(res.body.total).to.equal(2) | 404 | expect(res.body.total).to.equal(2) |
405 | 405 | ||
406 | const data: VideoPlaylist[] = res.body.data | 406 | const data: VideoPlaylist[] = res.body.data |
407 | expect(data).to.have.lengthOf(1) | 407 | expect(data).to.have.lengthOf(1) |
408 | expect(data[ 0 ].displayName).to.equal('playlist 3') | 408 | expect(data[0].displayName).to.equal('playlist 3') |
409 | } | 409 | } |
410 | 410 | ||
411 | { | 411 | { |
412 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 0, 10, 'createdAt', '3') | 412 | const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '3') |
413 | 413 | ||
414 | expect(res.body.total).to.equal(1) | 414 | expect(res.body.total).to.equal(1) |
415 | 415 | ||
416 | const data: VideoPlaylist[] = res.body.data | 416 | const data: VideoPlaylist[] = res.body.data |
417 | expect(data).to.have.lengthOf(1) | 417 | expect(data).to.have.lengthOf(1) |
418 | expect(data[ 0 ].displayName).to.equal('playlist 3') | 418 | expect(data[0].displayName).to.equal('playlist 3') |
419 | } | 419 | } |
420 | 420 | ||
421 | { | 421 | { |
422 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 0, 10, 'createdAt', '4') | 422 | const res = await getAccountPlaylistsList(servers[1].url, 'root', 0, 10, 'createdAt', '4') |
423 | 423 | ||
424 | expect(res.body.total).to.equal(0) | 424 | expect(res.body.total).to.equal(0) |
425 | 425 | ||
@@ -432,8 +432,8 @@ describe('Test video playlists', function () { | |||
432 | this.timeout(30000) | 432 | this.timeout(30000) |
433 | 433 | ||
434 | await createVideoPlaylist({ | 434 | await createVideoPlaylist({ |
435 | url: servers[ 1 ].url, | 435 | url: servers[1].url, |
436 | token: servers[ 1 ].accessToken, | 436 | token: servers[1].accessToken, |
437 | playlistAttrs: { | 437 | playlistAttrs: { |
438 | displayName: 'playlist unlisted', | 438 | displayName: 'playlist unlisted', |
439 | privacy: VideoPlaylistPrivacy.UNLISTED | 439 | privacy: VideoPlaylistPrivacy.UNLISTED |
@@ -441,8 +441,8 @@ describe('Test video playlists', function () { | |||
441 | }) | 441 | }) |
442 | 442 | ||
443 | await createVideoPlaylist({ | 443 | await createVideoPlaylist({ |
444 | url: servers[ 1 ].url, | 444 | url: servers[1].url, |
445 | token: servers[ 1 ].accessToken, | 445 | token: servers[1].accessToken, |
446 | playlistAttrs: { | 446 | playlistAttrs: { |
447 | displayName: 'playlist private', | 447 | displayName: 'playlist private', |
448 | privacy: VideoPlaylistPrivacy.PRIVATE | 448 | privacy: VideoPlaylistPrivacy.PRIVATE |
@@ -453,18 +453,18 @@ describe('Test video playlists', function () { | |||
453 | 453 | ||
454 | for (const server of servers) { | 454 | for (const server of servers) { |
455 | const results = [ | 455 | const results = [ |
456 | await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[ 1 ].port, 0, 5, '-createdAt'), | 456 | await getAccountPlaylistsList(server.url, 'root@localhost:' + servers[1].port, 0, 5, '-createdAt'), |
457 | await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') | 457 | await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') |
458 | ] | 458 | ] |
459 | 459 | ||
460 | expect(results[ 0 ].body.total).to.equal(2) | 460 | expect(results[0].body.total).to.equal(2) |
461 | expect(results[ 1 ].body.total).to.equal(3) | 461 | expect(results[1].body.total).to.equal(3) |
462 | 462 | ||
463 | for (const res of results) { | 463 | for (const res of results) { |
464 | const data: VideoPlaylist[] = res.body.data | 464 | const data: VideoPlaylist[] = res.body.data |
465 | expect(data).to.have.lengthOf(2) | 465 | expect(data).to.have.lengthOf(2) |
466 | expect(data[ 0 ].displayName).to.equal('playlist 3') | 466 | expect(data[0].displayName).to.equal('playlist 3') |
467 | expect(data[ 1 ].displayName).to.equal('playlist 2') | 467 | expect(data[1].displayName).to.equal('playlist 2') |
468 | } | 468 | } |
469 | } | 469 | } |
470 | }) | 470 | }) |
@@ -519,32 +519,32 @@ describe('Test video playlists', function () { | |||
519 | this.timeout(30000) | 519 | this.timeout(30000) |
520 | 520 | ||
521 | const addVideo = (elementAttrs: any) => { | 521 | const addVideo = (elementAttrs: any) => { |
522 | return addVideoInPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: playlistServer1Id, elementAttrs }) | 522 | return addVideoInPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: playlistServer1Id, elementAttrs }) |
523 | } | 523 | } |
524 | 524 | ||
525 | const res = await createVideoPlaylist({ | 525 | const res = await createVideoPlaylist({ |
526 | url: servers[ 0 ].url, | 526 | url: servers[0].url, |
527 | token: servers[ 0 ].accessToken, | 527 | token: servers[0].accessToken, |
528 | playlistAttrs: { | 528 | playlistAttrs: { |
529 | displayName: 'playlist 4', | 529 | displayName: 'playlist 4', |
530 | privacy: VideoPlaylistPrivacy.PUBLIC, | 530 | privacy: VideoPlaylistPrivacy.PUBLIC, |
531 | videoChannelId: servers[ 0 ].videoChannel.id | 531 | videoChannelId: servers[0].videoChannel.id |
532 | } | 532 | } |
533 | }) | 533 | }) |
534 | 534 | ||
535 | playlistServer1Id = res.body.videoPlaylist.id | 535 | playlistServer1Id = res.body.videoPlaylist.id |
536 | playlistServer1UUID = res.body.videoPlaylist.uuid | 536 | playlistServer1UUID = res.body.videoPlaylist.uuid |
537 | 537 | ||
538 | await addVideo({ videoId: servers[ 0 ].videos[ 0 ].uuid, startTimestamp: 15, stopTimestamp: 28 }) | 538 | await addVideo({ videoId: servers[0].videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 }) |
539 | await addVideo({ videoId: servers[ 2 ].videos[ 1 ].uuid, startTimestamp: 35 }) | 539 | await addVideo({ videoId: servers[2].videos[1].uuid, startTimestamp: 35 }) |
540 | await addVideo({ videoId: servers[ 2 ].videos[ 2 ].uuid }) | 540 | await addVideo({ videoId: servers[2].videos[2].uuid }) |
541 | { | 541 | { |
542 | const res = await addVideo({ videoId: servers[ 0 ].videos[ 3 ].uuid, stopTimestamp: 35 }) | 542 | const res = await addVideo({ videoId: servers[0].videos[3].uuid, stopTimestamp: 35 }) |
543 | playlistElementServer1Video4 = res.body.videoPlaylistElement.id | 543 | playlistElementServer1Video4 = res.body.videoPlaylistElement.id |
544 | } | 544 | } |
545 | 545 | ||
546 | { | 546 | { |
547 | const res = await addVideo({ videoId: servers[ 0 ].videos[ 4 ].uuid, startTimestamp: 45, stopTimestamp: 60 }) | 547 | const res = await addVideo({ videoId: servers[0].videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 }) |
548 | playlistElementServer1Video5 = res.body.videoPlaylistElement.id | 548 | playlistElementServer1Video5 = res.body.videoPlaylistElement.id |
549 | } | 549 | } |
550 | 550 | ||
@@ -567,35 +567,35 @@ describe('Test video playlists', function () { | |||
567 | const videoElements: VideoPlaylistElement[] = res.body.data | 567 | const videoElements: VideoPlaylistElement[] = res.body.data |
568 | expect(videoElements).to.have.lengthOf(6) | 568 | expect(videoElements).to.have.lengthOf(6) |
569 | 569 | ||
570 | expect(videoElements[ 0 ].video.name).to.equal('video 0 server 1') | 570 | expect(videoElements[0].video.name).to.equal('video 0 server 1') |
571 | expect(videoElements[ 0 ].position).to.equal(1) | 571 | expect(videoElements[0].position).to.equal(1) |
572 | expect(videoElements[ 0 ].startTimestamp).to.equal(15) | 572 | expect(videoElements[0].startTimestamp).to.equal(15) |
573 | expect(videoElements[ 0 ].stopTimestamp).to.equal(28) | 573 | expect(videoElements[0].stopTimestamp).to.equal(28) |
574 | 574 | ||
575 | expect(videoElements[ 1 ].video.name).to.equal('video 1 server 3') | 575 | expect(videoElements[1].video.name).to.equal('video 1 server 3') |
576 | expect(videoElements[ 1 ].position).to.equal(2) | 576 | expect(videoElements[1].position).to.equal(2) |
577 | expect(videoElements[ 1 ].startTimestamp).to.equal(35) | 577 | expect(videoElements[1].startTimestamp).to.equal(35) |
578 | expect(videoElements[ 1 ].stopTimestamp).to.be.null | 578 | expect(videoElements[1].stopTimestamp).to.be.null |
579 | 579 | ||
580 | expect(videoElements[ 2 ].video.name).to.equal('video 2 server 3') | 580 | expect(videoElements[2].video.name).to.equal('video 2 server 3') |
581 | expect(videoElements[ 2 ].position).to.equal(3) | 581 | expect(videoElements[2].position).to.equal(3) |
582 | expect(videoElements[ 2 ].startTimestamp).to.be.null | 582 | expect(videoElements[2].startTimestamp).to.be.null |
583 | expect(videoElements[ 2 ].stopTimestamp).to.be.null | 583 | expect(videoElements[2].stopTimestamp).to.be.null |
584 | 584 | ||
585 | expect(videoElements[ 3 ].video.name).to.equal('video 3 server 1') | 585 | expect(videoElements[3].video.name).to.equal('video 3 server 1') |
586 | expect(videoElements[ 3 ].position).to.equal(4) | 586 | expect(videoElements[3].position).to.equal(4) |
587 | expect(videoElements[ 3 ].startTimestamp).to.be.null | 587 | expect(videoElements[3].startTimestamp).to.be.null |
588 | expect(videoElements[ 3 ].stopTimestamp).to.equal(35) | 588 | expect(videoElements[3].stopTimestamp).to.equal(35) |
589 | 589 | ||
590 | expect(videoElements[ 4 ].video.name).to.equal('video 4 server 1') | 590 | expect(videoElements[4].video.name).to.equal('video 4 server 1') |
591 | expect(videoElements[ 4 ].position).to.equal(5) | 591 | expect(videoElements[4].position).to.equal(5) |
592 | expect(videoElements[ 4 ].startTimestamp).to.equal(45) | 592 | expect(videoElements[4].startTimestamp).to.equal(45) |
593 | expect(videoElements[ 4 ].stopTimestamp).to.equal(60) | 593 | expect(videoElements[4].stopTimestamp).to.equal(60) |
594 | 594 | ||
595 | expect(videoElements[ 5 ].video.name).to.equal('NSFW video') | 595 | expect(videoElements[5].video.name).to.equal('NSFW video') |
596 | expect(videoElements[ 5 ].position).to.equal(6) | 596 | expect(videoElements[5].position).to.equal(6) |
597 | expect(videoElements[ 5 ].startTimestamp).to.equal(5) | 597 | expect(videoElements[5].startTimestamp).to.equal(5) |
598 | expect(videoElements[ 5 ].stopTimestamp).to.be.null | 598 | expect(videoElements[5].stopTimestamp).to.be.null |
599 | 599 | ||
600 | const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) | 600 | const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) |
601 | expect(res3.body.data).to.have.lengthOf(2) | 601 | expect(res3.body.data).to.have.lengthOf(2) |
@@ -616,18 +616,18 @@ describe('Test video playlists', function () { | |||
616 | before(async function () { | 616 | before(async function () { |
617 | this.timeout(30000) | 617 | this.timeout(30000) |
618 | 618 | ||
619 | groupUser1 = [ Object.assign({}, servers[ 0 ], { accessToken: userAccessTokenServer1 }) ] | 619 | groupUser1 = [ Object.assign({}, servers[0], { accessToken: userAccessTokenServer1 }) ] |
620 | groupWithoutToken1 = [ Object.assign({}, servers[ 0 ], { accessToken: undefined }) ] | 620 | groupWithoutToken1 = [ Object.assign({}, servers[0], { accessToken: undefined }) ] |
621 | group1 = [ servers[ 0 ] ] | 621 | group1 = [ servers[0] ] |
622 | group2 = [ servers[ 1 ], servers[ 2 ] ] | 622 | group2 = [ servers[1], servers[2] ] |
623 | 623 | ||
624 | const res = await createVideoPlaylist({ | 624 | const res = await createVideoPlaylist({ |
625 | url: servers[ 0 ].url, | 625 | url: servers[0].url, |
626 | token: userAccessTokenServer1, | 626 | token: userAccessTokenServer1, |
627 | playlistAttrs: { | 627 | playlistAttrs: { |
628 | displayName: 'playlist 56', | 628 | displayName: 'playlist 56', |
629 | privacy: VideoPlaylistPrivacy.PUBLIC, | 629 | privacy: VideoPlaylistPrivacy.PUBLIC, |
630 | videoChannelId: servers[ 0 ].videoChannel.id | 630 | videoChannelId: servers[0].videoChannel.id |
631 | } | 631 | } |
632 | }) | 632 | }) |
633 | 633 | ||
@@ -635,7 +635,7 @@ describe('Test video playlists', function () { | |||
635 | playlistServer1UUID2 = res.body.videoPlaylist.uuid | 635 | playlistServer1UUID2 = res.body.videoPlaylist.uuid |
636 | 636 | ||
637 | const addVideo = (elementAttrs: any) => { | 637 | const addVideo = (elementAttrs: any) => { |
638 | return addVideoInPlaylist({ url: servers[ 0 ].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs }) | 638 | return addVideoInPlaylist({ url: servers[0].url, token: userAccessTokenServer1, playlistId: playlistServer1Id2, elementAttrs }) |
639 | } | 639 | } |
640 | 640 | ||
641 | video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid | 641 | video1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video 89', token: userAccessTokenServer1 })).uuid |
@@ -656,7 +656,7 @@ describe('Test video playlists', function () { | |||
656 | const position = 1 | 656 | const position = 1 |
657 | 657 | ||
658 | { | 658 | { |
659 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PRIVATE }) | 659 | await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PRIVATE }) |
660 | await waitJobs(servers) | 660 | await waitJobs(servers) |
661 | 661 | ||
662 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 662 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
@@ -666,7 +666,7 @@ describe('Test video playlists', function () { | |||
666 | } | 666 | } |
667 | 667 | ||
668 | { | 668 | { |
669 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, video1, { privacy: VideoPrivacy.PUBLIC }) | 669 | await updateVideo(servers[0].url, servers[0].accessToken, video1, { privacy: VideoPrivacy.PUBLIC }) |
670 | await waitJobs(servers) | 670 | await waitJobs(servers) |
671 | 671 | ||
672 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 672 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
@@ -684,7 +684,7 @@ describe('Test video playlists', function () { | |||
684 | const position = 1 | 684 | const position = 1 |
685 | 685 | ||
686 | { | 686 | { |
687 | await addVideoToBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1, 'reason', true) | 687 | await addVideoToBlacklist(servers[0].url, servers[0].accessToken, video1, 'reason', true) |
688 | await waitJobs(servers) | 688 | await waitJobs(servers) |
689 | 689 | ||
690 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 690 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
@@ -694,7 +694,7 @@ describe('Test video playlists', function () { | |||
694 | } | 694 | } |
695 | 695 | ||
696 | { | 696 | { |
697 | await removeVideoFromBlacklist(servers[ 0 ].url, servers[ 0 ].accessToken, video1) | 697 | await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, video1) |
698 | await waitJobs(servers) | 698 | await waitJobs(servers) |
699 | 699 | ||
700 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 700 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
@@ -712,52 +712,52 @@ describe('Test video playlists', function () { | |||
712 | const position = 2 | 712 | const position = 2 |
713 | 713 | ||
714 | { | 714 | { |
715 | await addAccountToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) | 715 | await addAccountToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) |
716 | await waitJobs(servers) | 716 | await waitJobs(servers) |
717 | 717 | ||
718 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) | 718 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
719 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 719 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
720 | 720 | ||
721 | await removeAccountFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) | 721 | await removeAccountFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'root@localhost:' + servers[1].port) |
722 | await waitJobs(servers) | 722 | await waitJobs(servers) |
723 | 723 | ||
724 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 724 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
725 | } | 725 | } |
726 | 726 | ||
727 | { | 727 | { |
728 | await addServerToAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port) | 728 | await addServerToAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port) |
729 | await waitJobs(servers) | 729 | await waitJobs(servers) |
730 | 730 | ||
731 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) | 731 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
732 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 732 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
733 | 733 | ||
734 | await removeServerFromAccountBlocklist(servers[ 0 ].url, userAccessTokenServer1, 'localhost:' + servers[1].port) | 734 | await removeServerFromAccountBlocklist(servers[0].url, userAccessTokenServer1, 'localhost:' + servers[1].port) |
735 | await waitJobs(servers) | 735 | await waitJobs(servers) |
736 | 736 | ||
737 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 737 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
738 | } | 738 | } |
739 | 739 | ||
740 | { | 740 | { |
741 | await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port) | 741 | await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port) |
742 | await waitJobs(servers) | 742 | await waitJobs(servers) |
743 | 743 | ||
744 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) | 744 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
745 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 745 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
746 | 746 | ||
747 | await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'root@localhost:' + servers[1].port) | 747 | await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port) |
748 | await waitJobs(servers) | 748 | await waitJobs(servers) |
749 | 749 | ||
750 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 750 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
751 | } | 751 | } |
752 | 752 | ||
753 | { | 753 | { |
754 | await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) | 754 | await addServerToServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) |
755 | await waitJobs(servers) | 755 | await waitJobs(servers) |
756 | 756 | ||
757 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) | 757 | await checkPlaylistElementType(groupUser1, playlistServer1UUID2, VideoPlaylistElementType.UNAVAILABLE, position, name, 3) |
758 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 758 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
759 | 759 | ||
760 | await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, 'localhost:' + servers[1].port) | 760 | await removeServerFromServerBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) |
761 | await waitJobs(servers) | 761 | await waitJobs(servers) |
762 | 762 | ||
763 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) | 763 | await checkPlaylistElementType(group2, playlistServer1UUID2, VideoPlaylistElementType.REGULAR, position, name, 3) |
@@ -785,8 +785,8 @@ describe('Test video playlists', function () { | |||
785 | 785 | ||
786 | { | 786 | { |
787 | await reorderVideosPlaylist({ | 787 | await reorderVideosPlaylist({ |
788 | url: servers[ 0 ].url, | 788 | url: servers[0].url, |
789 | token: servers[ 0 ].accessToken, | 789 | token: servers[0].accessToken, |
790 | playlistId: playlistServer1Id, | 790 | playlistId: playlistServer1Id, |
791 | elementAttrs: { | 791 | elementAttrs: { |
792 | startPosition: 2, | 792 | startPosition: 2, |
@@ -813,8 +813,8 @@ describe('Test video playlists', function () { | |||
813 | 813 | ||
814 | { | 814 | { |
815 | await reorderVideosPlaylist({ | 815 | await reorderVideosPlaylist({ |
816 | url: servers[ 0 ].url, | 816 | url: servers[0].url, |
817 | token: servers[ 0 ].accessToken, | 817 | token: servers[0].accessToken, |
818 | playlistId: playlistServer1Id, | 818 | playlistId: playlistServer1Id, |
819 | elementAttrs: { | 819 | elementAttrs: { |
820 | startPosition: 1, | 820 | startPosition: 1, |
@@ -842,8 +842,8 @@ describe('Test video playlists', function () { | |||
842 | 842 | ||
843 | { | 843 | { |
844 | await reorderVideosPlaylist({ | 844 | await reorderVideosPlaylist({ |
845 | url: servers[ 0 ].url, | 845 | url: servers[0].url, |
846 | token: servers[ 0 ].accessToken, | 846 | token: servers[0].accessToken, |
847 | playlistId: playlistServer1Id, | 847 | playlistId: playlistServer1Id, |
848 | elementAttrs: { | 848 | elementAttrs: { |
849 | startPosition: 6, | 849 | startPosition: 6, |
@@ -868,7 +868,7 @@ describe('Test video playlists', function () { | |||
868 | ]) | 868 | ]) |
869 | 869 | ||
870 | for (let i = 1; i <= elements.length; i++) { | 870 | for (let i = 1; i <= elements.length; i++) { |
871 | expect(elements[ i - 1 ].position).to.equal(i) | 871 | expect(elements[i - 1].position).to.equal(i) |
872 | } | 872 | } |
873 | } | 873 | } |
874 | } | 874 | } |
@@ -878,8 +878,8 @@ describe('Test video playlists', function () { | |||
878 | this.timeout(30000) | 878 | this.timeout(30000) |
879 | 879 | ||
880 | await updateVideoPlaylistElement({ | 880 | await updateVideoPlaylistElement({ |
881 | url: servers[ 0 ].url, | 881 | url: servers[0].url, |
882 | token: servers[ 0 ].accessToken, | 882 | token: servers[0].accessToken, |
883 | playlistId: playlistServer1Id, | 883 | playlistId: playlistServer1Id, |
884 | playlistElementId: playlistElementServer1Video4, | 884 | playlistElementId: playlistElementServer1Video4, |
885 | elementAttrs: { | 885 | elementAttrs: { |
@@ -888,8 +888,8 @@ describe('Test video playlists', function () { | |||
888 | }) | 888 | }) |
889 | 889 | ||
890 | await updateVideoPlaylistElement({ | 890 | await updateVideoPlaylistElement({ |
891 | url: servers[ 0 ].url, | 891 | url: servers[0].url, |
892 | token: servers[ 0 ].accessToken, | 892 | token: servers[0].accessToken, |
893 | playlistId: playlistServer1Id, | 893 | playlistId: playlistServer1Id, |
894 | playlistElementId: playlistElementServer1Video5, | 894 | playlistElementId: playlistElementServer1Video5, |
895 | elementAttrs: { | 895 | elementAttrs: { |
@@ -903,62 +903,62 @@ describe('Test video playlists', function () { | |||
903 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | 903 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) |
904 | const elements: VideoPlaylistElement[] = res.body.data | 904 | const elements: VideoPlaylistElement[] = res.body.data |
905 | 905 | ||
906 | expect(elements[ 0 ].video.name).to.equal('video 3 server 1') | 906 | expect(elements[0].video.name).to.equal('video 3 server 1') |
907 | expect(elements[ 0 ].position).to.equal(1) | 907 | expect(elements[0].position).to.equal(1) |
908 | expect(elements[ 0 ].startTimestamp).to.equal(1) | 908 | expect(elements[0].startTimestamp).to.equal(1) |
909 | expect(elements[ 0 ].stopTimestamp).to.equal(35) | 909 | expect(elements[0].stopTimestamp).to.equal(35) |
910 | 910 | ||
911 | expect(elements[ 5 ].video.name).to.equal('video 4 server 1') | 911 | expect(elements[5].video.name).to.equal('video 4 server 1') |
912 | expect(elements[ 5 ].position).to.equal(6) | 912 | expect(elements[5].position).to.equal(6) |
913 | expect(elements[ 5 ].startTimestamp).to.equal(45) | 913 | expect(elements[5].startTimestamp).to.equal(45) |
914 | expect(elements[ 5 ].stopTimestamp).to.be.null | 914 | expect(elements[5].stopTimestamp).to.be.null |
915 | } | 915 | } |
916 | }) | 916 | }) |
917 | 917 | ||
918 | it('Should check videos existence in my playlist', async function () { | 918 | it('Should check videos existence in my playlist', async function () { |
919 | const videoIds = [ | 919 | const videoIds = [ |
920 | servers[ 0 ].videos[ 0 ].id, | 920 | servers[0].videos[0].id, |
921 | 42000, | 921 | 42000, |
922 | servers[ 0 ].videos[ 3 ].id, | 922 | servers[0].videos[3].id, |
923 | 43000, | 923 | 43000, |
924 | servers[ 0 ].videos[ 4 ].id | 924 | servers[0].videos[4].id |
925 | ] | 925 | ] |
926 | const res = await doVideosExistInMyPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, videoIds) | 926 | const res = await doVideosExistInMyPlaylist(servers[0].url, servers[0].accessToken, videoIds) |
927 | const obj = res.body as VideoExistInPlaylist | 927 | const obj = res.body as VideoExistInPlaylist |
928 | 928 | ||
929 | { | 929 | { |
930 | const elem = obj[ servers[ 0 ].videos[ 0 ].id ] | 930 | const elem = obj[servers[0].videos[0].id] |
931 | expect(elem).to.have.lengthOf(1) | 931 | expect(elem).to.have.lengthOf(1) |
932 | expect(elem[ 0 ].playlistElementId).to.exist | 932 | expect(elem[0].playlistElementId).to.exist |
933 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | 933 | expect(elem[0].playlistId).to.equal(playlistServer1Id) |
934 | expect(elem[ 0 ].startTimestamp).to.equal(15) | 934 | expect(elem[0].startTimestamp).to.equal(15) |
935 | expect(elem[ 0 ].stopTimestamp).to.equal(28) | 935 | expect(elem[0].stopTimestamp).to.equal(28) |
936 | } | 936 | } |
937 | 937 | ||
938 | { | 938 | { |
939 | const elem = obj[ servers[ 0 ].videos[ 3 ].id ] | 939 | const elem = obj[servers[0].videos[3].id] |
940 | expect(elem).to.have.lengthOf(1) | 940 | expect(elem).to.have.lengthOf(1) |
941 | expect(elem[ 0 ].playlistElementId).to.equal(playlistElementServer1Video4) | 941 | expect(elem[0].playlistElementId).to.equal(playlistElementServer1Video4) |
942 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | 942 | expect(elem[0].playlistId).to.equal(playlistServer1Id) |
943 | expect(elem[ 0 ].startTimestamp).to.equal(1) | 943 | expect(elem[0].startTimestamp).to.equal(1) |
944 | expect(elem[ 0 ].stopTimestamp).to.equal(35) | 944 | expect(elem[0].stopTimestamp).to.equal(35) |
945 | } | 945 | } |
946 | 946 | ||
947 | { | 947 | { |
948 | const elem = obj[ servers[ 0 ].videos[ 4 ].id ] | 948 | const elem = obj[servers[0].videos[4].id] |
949 | expect(elem).to.have.lengthOf(1) | 949 | expect(elem).to.have.lengthOf(1) |
950 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | 950 | expect(elem[0].playlistId).to.equal(playlistServer1Id) |
951 | expect(elem[ 0 ].startTimestamp).to.equal(45) | 951 | expect(elem[0].startTimestamp).to.equal(45) |
952 | expect(elem[ 0 ].stopTimestamp).to.equal(null) | 952 | expect(elem[0].stopTimestamp).to.equal(null) |
953 | } | 953 | } |
954 | 954 | ||
955 | expect(obj[ 42000 ]).to.have.lengthOf(0) | 955 | expect(obj[42000]).to.have.lengthOf(0) |
956 | expect(obj[ 43000 ]).to.have.lengthOf(0) | 956 | expect(obj[43000]).to.have.lengthOf(0) |
957 | }) | 957 | }) |
958 | 958 | ||
959 | it('Should automatically update updatedAt field of playlists', async function () { | 959 | it('Should automatically update updatedAt field of playlists', async function () { |
960 | const server = servers[ 1 ] | 960 | const server = servers[1] |
961 | const videoId = servers[ 1 ].videos[ 5 ].id | 961 | const videoId = servers[1].videos[5].id |
962 | 962 | ||
963 | async function getPlaylistNames () { | 963 | async function getPlaylistNames () { |
964 | const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt') | 964 | const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt') |
@@ -974,8 +974,8 @@ describe('Test video playlists', function () { | |||
974 | const element2 = res2.body.videoPlaylistElement.id | 974 | const element2 = res2.body.videoPlaylistElement.id |
975 | 975 | ||
976 | const names1 = await getPlaylistNames() | 976 | const names1 = await getPlaylistNames() |
977 | expect(names1[ 0 ]).to.equal('playlist 3 updated') | 977 | expect(names1[0]).to.equal('playlist 3 updated') |
978 | expect(names1[ 1 ]).to.equal('playlist 2') | 978 | expect(names1[1]).to.equal('playlist 2') |
979 | 979 | ||
980 | await removeVideoFromPlaylist({ | 980 | await removeVideoFromPlaylist({ |
981 | url: server.url, | 981 | url: server.url, |
@@ -985,8 +985,8 @@ describe('Test video playlists', function () { | |||
985 | }) | 985 | }) |
986 | 986 | ||
987 | const names2 = await getPlaylistNames() | 987 | const names2 = await getPlaylistNames() |
988 | expect(names2[ 0 ]).to.equal('playlist 2') | 988 | expect(names2[0]).to.equal('playlist 2') |
989 | expect(names2[ 1 ]).to.equal('playlist 3 updated') | 989 | expect(names2[1]).to.equal('playlist 3 updated') |
990 | 990 | ||
991 | await removeVideoFromPlaylist({ | 991 | await removeVideoFromPlaylist({ |
992 | url: server.url, | 992 | url: server.url, |
@@ -996,23 +996,23 @@ describe('Test video playlists', function () { | |||
996 | }) | 996 | }) |
997 | 997 | ||
998 | const names3 = await getPlaylistNames() | 998 | const names3 = await getPlaylistNames() |
999 | expect(names3[ 0 ]).to.equal('playlist 3 updated') | 999 | expect(names3[0]).to.equal('playlist 3 updated') |
1000 | expect(names3[ 1 ]).to.equal('playlist 2') | 1000 | expect(names3[1]).to.equal('playlist 2') |
1001 | }) | 1001 | }) |
1002 | 1002 | ||
1003 | it('Should delete some elements', async function () { | 1003 | it('Should delete some elements', async function () { |
1004 | this.timeout(30000) | 1004 | this.timeout(30000) |
1005 | 1005 | ||
1006 | await removeVideoFromPlaylist({ | 1006 | await removeVideoFromPlaylist({ |
1007 | url: servers[ 0 ].url, | 1007 | url: servers[0].url, |
1008 | token: servers[ 0 ].accessToken, | 1008 | token: servers[0].accessToken, |
1009 | playlistId: playlistServer1Id, | 1009 | playlistId: playlistServer1Id, |
1010 | playlistElementId: playlistElementServer1Video4 | 1010 | playlistElementId: playlistElementServer1Video4 |
1011 | }) | 1011 | }) |
1012 | 1012 | ||
1013 | await removeVideoFromPlaylist({ | 1013 | await removeVideoFromPlaylist({ |
1014 | url: servers[ 0 ].url, | 1014 | url: servers[0].url, |
1015 | token: servers[ 0 ].accessToken, | 1015 | token: servers[0].accessToken, |
1016 | playlistId: playlistServer1Id, | 1016 | playlistId: playlistServer1Id, |
1017 | playlistElementId: playlistElementNSFW | 1017 | playlistElementId: playlistElementNSFW |
1018 | }) | 1018 | }) |
@@ -1027,17 +1027,17 @@ describe('Test video playlists', function () { | |||
1027 | const elements: VideoPlaylistElement[] = res.body.data | 1027 | const elements: VideoPlaylistElement[] = res.body.data |
1028 | expect(elements).to.have.lengthOf(4) | 1028 | expect(elements).to.have.lengthOf(4) |
1029 | 1029 | ||
1030 | expect(elements[ 0 ].video.name).to.equal('video 0 server 1') | 1030 | expect(elements[0].video.name).to.equal('video 0 server 1') |
1031 | expect(elements[ 0 ].position).to.equal(1) | 1031 | expect(elements[0].position).to.equal(1) |
1032 | 1032 | ||
1033 | expect(elements[ 1 ].video.name).to.equal('video 2 server 3') | 1033 | expect(elements[1].video.name).to.equal('video 2 server 3') |
1034 | expect(elements[ 1 ].position).to.equal(2) | 1034 | expect(elements[1].position).to.equal(2) |
1035 | 1035 | ||
1036 | expect(elements[ 2 ].video.name).to.equal('video 1 server 3') | 1036 | expect(elements[2].video.name).to.equal('video 1 server 3') |
1037 | expect(elements[ 2 ].position).to.equal(3) | 1037 | expect(elements[2].position).to.equal(3) |
1038 | 1038 | ||
1039 | expect(elements[ 3 ].video.name).to.equal('video 4 server 1') | 1039 | expect(elements[3].video.name).to.equal('video 4 server 1') |
1040 | expect(elements[ 3 ].position).to.equal(4) | 1040 | expect(elements[3].position).to.equal(4) |
1041 | } | 1041 | } |
1042 | }) | 1042 | }) |
1043 | 1043 | ||
@@ -1045,12 +1045,12 @@ describe('Test video playlists', function () { | |||
1045 | this.timeout(30000) | 1045 | this.timeout(30000) |
1046 | 1046 | ||
1047 | const res = await createVideoPlaylist({ | 1047 | const res = await createVideoPlaylist({ |
1048 | url: servers[ 0 ].url, | 1048 | url: servers[0].url, |
1049 | token: servers[ 0 ].accessToken, | 1049 | token: servers[0].accessToken, |
1050 | playlistAttrs: { | 1050 | playlistAttrs: { |
1051 | displayName: 'my super public playlist', | 1051 | displayName: 'my super public playlist', |
1052 | privacy: VideoPlaylistPrivacy.PUBLIC, | 1052 | privacy: VideoPlaylistPrivacy.PUBLIC, |
1053 | videoChannelId: servers[ 0 ].videoChannel.id | 1053 | videoChannelId: servers[0].videoChannel.id |
1054 | } | 1054 | } |
1055 | }) | 1055 | }) |
1056 | const videoPlaylistIds = res.body.videoPlaylist | 1056 | const videoPlaylistIds = res.body.videoPlaylist |
@@ -1062,16 +1062,16 @@ describe('Test video playlists', function () { | |||
1062 | } | 1062 | } |
1063 | 1063 | ||
1064 | const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE } | 1064 | const playlistAttrs = { privacy: VideoPlaylistPrivacy.PRIVATE } |
1065 | await updateVideoPlaylist({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs }) | 1065 | await updateVideoPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: videoPlaylistIds.id, playlistAttrs }) |
1066 | 1066 | ||
1067 | await waitJobs(servers) | 1067 | await waitJobs(servers) |
1068 | 1068 | ||
1069 | for (const server of [ servers[ 1 ], servers[ 2 ] ]) { | 1069 | for (const server of [ servers[1], servers[2] ]) { |
1070 | await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404) | 1070 | await getVideoPlaylist(server.url, videoPlaylistIds.uuid, 404) |
1071 | } | 1071 | } |
1072 | await getVideoPlaylist(servers[ 0 ].url, videoPlaylistIds.uuid, 401) | 1072 | await getVideoPlaylist(servers[0].url, videoPlaylistIds.uuid, 401) |
1073 | 1073 | ||
1074 | await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistIds.uuid, 200) | 1074 | await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistIds.uuid, 200) |
1075 | }) | 1075 | }) |
1076 | }) | 1076 | }) |
1077 | 1077 | ||
@@ -1080,7 +1080,7 @@ describe('Test video playlists', function () { | |||
1080 | it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { | 1080 | it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { |
1081 | this.timeout(30000) | 1081 | this.timeout(30000) |
1082 | 1082 | ||
1083 | await deleteVideoPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, playlistServer1Id) | 1083 | await deleteVideoPlaylist(servers[0].url, servers[0].accessToken, playlistServer1Id) |
1084 | 1084 | ||
1085 | await waitJobs(servers) | 1085 | await waitJobs(servers) |
1086 | 1086 | ||
@@ -1103,15 +1103,15 @@ describe('Test video playlists', function () { | |||
1103 | const finder = data => data.find(p => p.displayName === 'my super playlist') | 1103 | const finder = data => data.find(p => p.displayName === 'my super playlist') |
1104 | 1104 | ||
1105 | { | 1105 | { |
1106 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | 1106 | const res = await getVideoPlaylistsList(servers[2].url, 0, 5) |
1107 | expect(res.body.total).to.equal(3) | 1107 | expect(res.body.total).to.equal(3) |
1108 | expect(finder(res.body.data)).to.not.be.undefined | 1108 | expect(finder(res.body.data)).to.not.be.undefined |
1109 | } | 1109 | } |
1110 | 1110 | ||
1111 | await unfollow(servers[ 2 ].url, servers[ 2 ].accessToken, servers[ 0 ]) | 1111 | await unfollow(servers[2].url, servers[2].accessToken, servers[0]) |
1112 | 1112 | ||
1113 | { | 1113 | { |
1114 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | 1114 | const res = await getVideoPlaylistsList(servers[2].url, 0, 5) |
1115 | expect(res.body.total).to.equal(1) | 1115 | expect(res.body.total).to.equal(1) |
1116 | 1116 | ||
1117 | expect(finder(res.body.data)).to.be.undefined | 1117 | expect(finder(res.body.data)).to.be.undefined |
@@ -1121,12 +1121,12 @@ describe('Test video playlists', function () { | |||
1121 | it('Should delete a channel and put the associated playlist in private mode', async function () { | 1121 | it('Should delete a channel and put the associated playlist in private mode', async function () { |
1122 | this.timeout(30000) | 1122 | this.timeout(30000) |
1123 | 1123 | ||
1124 | const res = await addVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'super_channel', displayName: 'super channel' }) | 1124 | const res = await addVideoChannel(servers[0].url, servers[0].accessToken, { name: 'super_channel', displayName: 'super channel' }) |
1125 | const videoChannelId = res.body.videoChannel.id | 1125 | const videoChannelId = res.body.videoChannel.id |
1126 | 1126 | ||
1127 | const res2 = await createVideoPlaylist({ | 1127 | const res2 = await createVideoPlaylist({ |
1128 | url: servers[ 0 ].url, | 1128 | url: servers[0].url, |
1129 | token: servers[ 0 ].accessToken, | 1129 | token: servers[0].accessToken, |
1130 | playlistAttrs: { | 1130 | playlistAttrs: { |
1131 | displayName: 'channel playlist', | 1131 | displayName: 'channel playlist', |
1132 | privacy: VideoPlaylistPrivacy.PUBLIC, | 1132 | privacy: VideoPlaylistPrivacy.PUBLIC, |
@@ -1137,15 +1137,15 @@ describe('Test video playlists', function () { | |||
1137 | 1137 | ||
1138 | await waitJobs(servers) | 1138 | await waitJobs(servers) |
1139 | 1139 | ||
1140 | await deleteVideoChannel(servers[ 0 ].url, servers[ 0 ].accessToken, 'super_channel') | 1140 | await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'super_channel') |
1141 | 1141 | ||
1142 | await waitJobs(servers) | 1142 | await waitJobs(servers) |
1143 | 1143 | ||
1144 | const res3 = await getVideoPlaylistWithToken(servers[ 0 ].url, servers[ 0 ].accessToken, videoPlaylistUUID) | 1144 | const res3 = await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistUUID) |
1145 | expect(res3.body.displayName).to.equal('channel playlist') | 1145 | expect(res3.body.displayName).to.equal('channel playlist') |
1146 | expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) | 1146 | expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) |
1147 | 1147 | ||
1148 | await getVideoPlaylist(servers[ 1 ].url, videoPlaylistUUID, 404) | 1148 | await getVideoPlaylist(servers[1].url, videoPlaylistUUID, 404) |
1149 | }) | 1149 | }) |
1150 | 1150 | ||
1151 | it('Should delete an account and delete its playlists', async function () { | 1151 | it('Should delete an account and delete its playlists', async function () { |
@@ -1153,20 +1153,20 @@ describe('Test video playlists', function () { | |||
1153 | 1153 | ||
1154 | const user = { username: 'user_1', password: 'password' } | 1154 | const user = { username: 'user_1', password: 'password' } |
1155 | const res = await createUser({ | 1155 | const res = await createUser({ |
1156 | url: servers[ 0 ].url, | 1156 | url: servers[0].url, |
1157 | accessToken: servers[ 0 ].accessToken, | 1157 | accessToken: servers[0].accessToken, |
1158 | username: user.username, | 1158 | username: user.username, |
1159 | password: user.password | 1159 | password: user.password |
1160 | }) | 1160 | }) |
1161 | 1161 | ||
1162 | const userId = res.body.user.id | 1162 | const userId = res.body.user.id |
1163 | const userAccessToken = await userLogin(servers[ 0 ], user) | 1163 | const userAccessToken = await userLogin(servers[0], user) |
1164 | 1164 | ||
1165 | const resChannel = await getMyUserInformation(servers[ 0 ].url, userAccessToken) | 1165 | const resChannel = await getMyUserInformation(servers[0].url, userAccessToken) |
1166 | const userChannel = (resChannel.body as User).videoChannels[ 0 ] | 1166 | const userChannel = (resChannel.body as User).videoChannels[0] |
1167 | 1167 | ||
1168 | await createVideoPlaylist({ | 1168 | await createVideoPlaylist({ |
1169 | url: servers[ 0 ].url, | 1169 | url: servers[0].url, |
1170 | token: userAccessToken, | 1170 | token: userAccessToken, |
1171 | playlistAttrs: { | 1171 | playlistAttrs: { |
1172 | displayName: 'playlist to be deleted', | 1172 | displayName: 'playlist to be deleted', |
@@ -1180,17 +1180,17 @@ describe('Test video playlists', function () { | |||
1180 | const finder = data => data.find(p => p.displayName === 'playlist to be deleted') | 1180 | const finder = data => data.find(p => p.displayName === 'playlist to be deleted') |
1181 | 1181 | ||
1182 | { | 1182 | { |
1183 | for (const server of [ servers[ 0 ], servers[ 1 ] ]) { | 1183 | for (const server of [ servers[0], servers[1] ]) { |
1184 | const res = await getVideoPlaylistsList(server.url, 0, 15) | 1184 | const res = await getVideoPlaylistsList(server.url, 0, 15) |
1185 | expect(finder(res.body.data)).to.not.be.undefined | 1185 | expect(finder(res.body.data)).to.not.be.undefined |
1186 | } | 1186 | } |
1187 | } | 1187 | } |
1188 | 1188 | ||
1189 | await removeUser(servers[ 0 ].url, userId, servers[ 0 ].accessToken) | 1189 | await removeUser(servers[0].url, userId, servers[0].accessToken) |
1190 | await waitJobs(servers) | 1190 | await waitJobs(servers) |
1191 | 1191 | ||
1192 | { | 1192 | { |
1193 | for (const server of [ servers[ 0 ], servers[ 1 ] ]) { | 1193 | for (const server of [ servers[0], servers[1] ]) { |
1194 | const res = await getVideoPlaylistsList(server.url, 0, 15) | 1194 | const res = await getVideoPlaylistsList(server.url, 0, 15) |
1195 | expect(finder(res.body.data)).to.be.undefined | 1195 | expect(finder(res.body.data)).to.be.undefined |
1196 | } | 1196 | } |
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts index e630ca84a..4bbbb90f3 100644 --- a/server/tests/api/videos/video-privacy.ts +++ b/server/tests/api/videos/video-privacy.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -6,7 +6,8 @@ import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enu | |||
6 | import { | 6 | import { |
7 | cleanupTests, | 7 | cleanupTests, |
8 | flushAndRunMultipleServers, | 8 | flushAndRunMultipleServers, |
9 | getVideosList, getVideosListWithToken, | 9 | getVideosList, |
10 | getVideosListWithToken, | ||
10 | ServerInfo, | 11 | ServerInfo, |
11 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
12 | uploadVideo | 13 | uploadVideo |
@@ -110,7 +111,7 @@ describe('Test video privacy', function () { | |||
110 | username: 'hello', | 111 | username: 'hello', |
111 | password: 'super password' | 112 | password: 'super password' |
112 | } | 113 | } |
113 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) | 114 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: user.username, password: user.password }) |
114 | 115 | ||
115 | anotherUserToken = await userLogin(servers[0], user) | 116 | anotherUserToken = await userLogin(servers[0], user) |
116 | await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, 403) | 117 | await getVideoWithToken(servers[0].url, anotherUserToken, privateVideoUUID, 403) |
@@ -174,7 +175,7 @@ describe('Test video privacy', function () { | |||
174 | privacy: VideoPrivacy.PUBLIC | 175 | privacy: VideoPrivacy.PUBLIC |
175 | } | 176 | } |
176 | 177 | ||
177 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, privateVideoId, attribute) | 178 | await updateVideo(servers[0].url, servers[0].accessToken, privateVideoId, attribute) |
178 | } | 179 | } |
179 | 180 | ||
180 | { | 181 | { |
@@ -182,7 +183,7 @@ describe('Test video privacy', function () { | |||
182 | name: 'internal video becomes public', | 183 | name: 'internal video becomes public', |
183 | privacy: VideoPrivacy.PUBLIC | 184 | privacy: VideoPrivacy.PUBLIC |
184 | } | 185 | } |
185 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, internalVideoId, attribute) | 186 | await updateVideo(servers[0].url, servers[0].accessToken, internalVideoId, attribute) |
186 | } | 187 | } |
187 | 188 | ||
188 | await waitJobs(servers) | 189 | await waitJobs(servers) |
diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts index 65a8eafb8..204f43611 100644 --- a/server/tests/api/videos/video-schedule-update.ts +++ b/server/tests/api/videos/video-schedule-update.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -10,7 +10,6 @@ import { | |||
10 | getMyVideos, | 10 | getMyVideos, |
11 | getVideosList, | 11 | getVideosList, |
12 | getVideoWithToken, | 12 | getVideoWithToken, |
13 | killallServers, | ||
14 | ServerInfo, | 13 | ServerInfo, |
15 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
16 | updateVideo, | 15 | updateVideo, |
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts index 4be74901a..13b3530b1 100644 --- a/server/tests/api/videos/video-transcoder.ts +++ b/server/tests/api/videos/video-transcoder.ts | |||
@@ -1,29 +1,40 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { omit } from 'lodash' | 5 | import { omit } from 'lodash' |
6 | import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos' | 6 | import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos' |
7 | import { audio, canDoQuickTranscode, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 7 | import { |
8 | audio, | ||
9 | canDoQuickTranscode, | ||
10 | getVideoFileBitrate, | ||
11 | getVideoFileFPS, | ||
12 | getVideoFileResolution, | ||
13 | getMetadataFromFile | ||
14 | } from '../../../helpers/ffmpeg-utils' | ||
8 | import { | 15 | import { |
9 | buildAbsoluteFixturePath, | 16 | buildAbsoluteFixturePath, |
10 | cleanupTests, | 17 | cleanupTests, |
11 | doubleFollow, | 18 | doubleFollow, |
12 | flushAndRunMultipleServers, | 19 | flushAndRunMultipleServers, |
13 | generateHighBitrateVideo, | 20 | generateHighBitrateVideo, |
21 | generateVideoWithFramerate, | ||
14 | getMyVideos, | 22 | getMyVideos, |
15 | getVideo, | 23 | getVideo, |
24 | getVideoFileMetadataUrl, | ||
16 | getVideosList, | 25 | getVideosList, |
17 | makeGetRequest, | 26 | makeGetRequest, |
18 | root, | 27 | root, |
19 | ServerInfo, | 28 | ServerInfo, |
20 | setAccessTokensToServers, | 29 | setAccessTokensToServers, |
21 | uploadVideo, | 30 | uploadVideo, uploadVideoAndGetId, |
22 | waitJobs, | 31 | waitJobs, |
23 | webtorrentAdd | 32 | webtorrentAdd |
24 | } from '../../../../shared/extra-utils' | 33 | } from '../../../../shared/extra-utils' |
25 | import { join } from 'path' | 34 | import { join } from 'path' |
26 | import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' | 35 | import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' |
36 | import { FfprobeData } from 'fluent-ffmpeg' | ||
37 | import { VideoFileMetadata } from '@shared/models/videos/video-file-metadata' | ||
27 | 38 | ||
28 | const expect = chai.expect | 39 | const expect = chai.expect |
29 | 40 | ||
@@ -55,19 +66,19 @@ describe('Test video transcoding', function () { | |||
55 | 66 | ||
56 | for (const server of servers) { | 67 | for (const server of servers) { |
57 | const res = await getVideosList(server.url) | 68 | const res = await getVideosList(server.url) |
58 | const video = res.body.data[ 0 ] | 69 | const video = res.body.data[0] |
59 | 70 | ||
60 | const res2 = await getVideo(server.url, video.id) | 71 | const res2 = await getVideo(server.url, video.id) |
61 | const videoDetails = res2.body | 72 | const videoDetails = res2.body |
62 | expect(videoDetails.files).to.have.lengthOf(1) | 73 | expect(videoDetails.files).to.have.lengthOf(1) |
63 | 74 | ||
64 | const magnetUri = videoDetails.files[ 0 ].magnetUri | 75 | const magnetUri = videoDetails.files[0].magnetUri |
65 | expect(magnetUri).to.match(/\.webm/) | 76 | expect(magnetUri).to.match(/\.webm/) |
66 | 77 | ||
67 | const torrent = await webtorrentAdd(magnetUri, true) | 78 | const torrent = await webtorrentAdd(magnetUri, true) |
68 | expect(torrent.files).to.be.an('array') | 79 | expect(torrent.files).to.be.an('array') |
69 | expect(torrent.files.length).to.equal(1) | 80 | expect(torrent.files.length).to.equal(1) |
70 | expect(torrent.files[ 0 ].path).match(/\.webm$/) | 81 | expect(torrent.files[0].path).match(/\.webm$/) |
71 | } | 82 | } |
72 | }) | 83 | }) |
73 | 84 | ||
@@ -92,13 +103,13 @@ describe('Test video transcoding', function () { | |||
92 | 103 | ||
93 | expect(videoDetails.files).to.have.lengthOf(4) | 104 | expect(videoDetails.files).to.have.lengthOf(4) |
94 | 105 | ||
95 | const magnetUri = videoDetails.files[ 0 ].magnetUri | 106 | const magnetUri = videoDetails.files[0].magnetUri |
96 | expect(magnetUri).to.match(/\.mp4/) | 107 | expect(magnetUri).to.match(/\.mp4/) |
97 | 108 | ||
98 | const torrent = await webtorrentAdd(magnetUri, true) | 109 | const torrent = await webtorrentAdd(magnetUri, true) |
99 | expect(torrent.files).to.be.an('array') | 110 | expect(torrent.files).to.be.an('array') |
100 | expect(torrent.files.length).to.equal(1) | 111 | expect(torrent.files.length).to.equal(1) |
101 | expect(torrent.files[ 0 ].path).match(/\.mp4$/) | 112 | expect(torrent.files[0].path).match(/\.mp4$/) |
102 | } | 113 | } |
103 | }) | 114 | }) |
104 | 115 | ||
@@ -126,8 +137,8 @@ describe('Test video transcoding', function () { | |||
126 | const probe = await audio.get(path) | 137 | const probe = await audio.get(path) |
127 | 138 | ||
128 | if (probe.audioStream) { | 139 | if (probe.audioStream) { |
129 | expect(probe.audioStream[ 'codec_name' ]).to.be.equal('aac') | 140 | expect(probe.audioStream['codec_name']).to.be.equal('aac') |
130 | expect(probe.audioStream[ 'bit_rate' ]).to.be.at.most(384 * 8000) | 141 | expect(probe.audioStream['bit_rate']).to.be.at.most(384 * 8000) |
131 | } else { | 142 | } else { |
132 | this.fail('Could not retrieve the audio stream on ' + probe.absolutePath) | 143 | this.fail('Could not retrieve the audio stream on ' + probe.absolutePath) |
133 | } | 144 | } |
@@ -211,10 +222,10 @@ describe('Test video transcoding', function () { | |||
211 | const videoDetails: VideoDetails = res2.body | 222 | const videoDetails: VideoDetails = res2.body |
212 | 223 | ||
213 | expect(videoDetails.files).to.have.lengthOf(4) | 224 | expect(videoDetails.files).to.have.lengthOf(4) |
214 | expect(videoDetails.files[ 0 ].fps).to.be.above(58).and.below(62) | 225 | expect(videoDetails.files[0].fps).to.be.above(58).and.below(62) |
215 | expect(videoDetails.files[ 1 ].fps).to.be.below(31) | 226 | expect(videoDetails.files[1].fps).to.be.below(31) |
216 | expect(videoDetails.files[ 2 ].fps).to.be.below(31) | 227 | expect(videoDetails.files[2].fps).to.be.below(31) |
217 | expect(videoDetails.files[ 3 ].fps).to.be.below(31) | 228 | expect(videoDetails.files[3].fps).to.be.below(31) |
218 | 229 | ||
219 | for (const resolution of [ '240', '360', '480' ]) { | 230 | for (const resolution of [ '240', '360', '480' ]) { |
220 | const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4') | 231 | const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4') |
@@ -240,11 +251,11 @@ describe('Test video transcoding', function () { | |||
240 | fixture: 'video_short1.webm', | 251 | fixture: 'video_short1.webm', |
241 | waitTranscoding: true | 252 | waitTranscoding: true |
242 | } | 253 | } |
243 | const resVideo = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes) | 254 | const resVideo = await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) |
244 | const videoId = resVideo.body.video.uuid | 255 | const videoId = resVideo.body.video.uuid |
245 | 256 | ||
246 | // Should be in transcode state | 257 | // Should be in transcode state |
247 | const { body } = await getVideo(servers[ 1 ].url, videoId) | 258 | const { body } = await getVideo(servers[1].url, videoId) |
248 | expect(body.name).to.equal('waiting video') | 259 | expect(body.name).to.equal('waiting video') |
249 | expect(body.state.id).to.equal(VideoState.TO_TRANSCODE) | 260 | expect(body.state.id).to.equal(VideoState.TO_TRANSCODE) |
250 | expect(body.state.label).to.equal('To transcode') | 261 | expect(body.state.label).to.equal('To transcode') |
@@ -310,7 +321,7 @@ describe('Test video transcoding', function () { | |||
310 | 321 | ||
311 | const video = res.body.data.find(v => v.name === videoAttributes.name) | 322 | const video = res.body.data.find(v => v.name === videoAttributes.name) |
312 | 323 | ||
313 | for (const resolution of ['240', '360', '480', '720', '1080']) { | 324 | for (const resolution of [ '240', '360', '480', '720', '1080' ]) { |
314 | const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4') | 325 | const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-' + resolution + '.mp4') |
315 | const bitrate = await getVideoFileBitrate(path) | 326 | const bitrate = await getVideoFileBitrate(path) |
316 | const fps = await getVideoFileFPS(path) | 327 | const fps = await getVideoFileFPS(path) |
@@ -340,7 +351,7 @@ describe('Test video transcoding', function () { | |||
340 | fixture | 351 | fixture |
341 | } | 352 | } |
342 | 353 | ||
343 | await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributes) | 354 | await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) |
344 | 355 | ||
345 | await waitJobs(servers) | 356 | await waitJobs(servers) |
346 | 357 | ||
@@ -353,7 +364,7 @@ describe('Test video transcoding', function () { | |||
353 | 364 | ||
354 | expect(videoDetails.files).to.have.lengthOf(4) | 365 | expect(videoDetails.files).to.have.lengthOf(4) |
355 | 366 | ||
356 | const magnetUri = videoDetails.files[ 0 ].magnetUri | 367 | const magnetUri = videoDetails.files[0].magnetUri |
357 | expect(magnetUri).to.contain('.mp4') | 368 | expect(magnetUri).to.contain('.mp4') |
358 | } | 369 | } |
359 | } | 370 | } |
@@ -370,7 +381,7 @@ describe('Test video transcoding', function () { | |||
370 | this.timeout(60000) | 381 | this.timeout(60000) |
371 | 382 | ||
372 | const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } | 383 | const videoAttributesArg = { name: 'audio_with_preview', previewfile: 'preview.jpg', fixture: 'sample.ogg' } |
373 | await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg) | 384 | await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg) |
374 | 385 | ||
375 | await waitJobs(servers) | 386 | await waitJobs(servers) |
376 | 387 | ||
@@ -386,7 +397,7 @@ describe('Test video transcoding', function () { | |||
386 | await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 }) | 397 | await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 }) |
387 | await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 }) | 398 | await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 }) |
388 | 399 | ||
389 | const magnetUri = videoDetails.files[ 0 ].magnetUri | 400 | const magnetUri = videoDetails.files[0].magnetUri |
390 | expect(magnetUri).to.contain('.mp4') | 401 | expect(magnetUri).to.contain('.mp4') |
391 | } | 402 | } |
392 | }) | 403 | }) |
@@ -395,7 +406,7 @@ describe('Test video transcoding', function () { | |||
395 | this.timeout(60000) | 406 | this.timeout(60000) |
396 | 407 | ||
397 | const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' } | 408 | const videoAttributesArg = { name: 'audio_without_preview', fixture: 'sample.ogg' } |
398 | await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, videoAttributesArg) | 409 | await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributesArg) |
399 | 410 | ||
400 | await waitJobs(servers) | 411 | await waitJobs(servers) |
401 | 412 | ||
@@ -411,11 +422,109 @@ describe('Test video transcoding', function () { | |||
411 | await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 }) | 422 | await makeGetRequest({ url: server.url, path: videoDetails.thumbnailPath, statusCodeExpected: 200 }) |
412 | await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 }) | 423 | await makeGetRequest({ url: server.url, path: videoDetails.previewPath, statusCodeExpected: 200 }) |
413 | 424 | ||
414 | const magnetUri = videoDetails.files[ 0 ].magnetUri | 425 | const magnetUri = videoDetails.files[0].magnetUri |
415 | expect(magnetUri).to.contain('.mp4') | 426 | expect(magnetUri).to.contain('.mp4') |
416 | } | 427 | } |
417 | }) | 428 | }) |
418 | 429 | ||
430 | it('Should downscale to the closest divisor standard framerate', async function () { | ||
431 | this.timeout(160000) | ||
432 | |||
433 | let tempFixturePath: string | ||
434 | |||
435 | { | ||
436 | tempFixturePath = await generateVideoWithFramerate(59) | ||
437 | |||
438 | const fps = await getVideoFileFPS(tempFixturePath) | ||
439 | expect(fps).to.be.equal(59) | ||
440 | } | ||
441 | |||
442 | const videoAttributes = { | ||
443 | name: '59fps video', | ||
444 | description: '59fps video', | ||
445 | fixture: tempFixturePath | ||
446 | } | ||
447 | |||
448 | await uploadVideo(servers[1].url, servers[1].accessToken, videoAttributes) | ||
449 | |||
450 | await waitJobs(servers) | ||
451 | |||
452 | for (const server of servers) { | ||
453 | const res = await getVideosList(server.url) | ||
454 | |||
455 | const video = res.body.data.find(v => v.name === videoAttributes.name) | ||
456 | |||
457 | { | ||
458 | const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-240.mp4') | ||
459 | const fps = await getVideoFileFPS(path) | ||
460 | expect(fps).to.be.equal(25) | ||
461 | } | ||
462 | |||
463 | { | ||
464 | const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', video.uuid + '-720.mp4') | ||
465 | const fps = await getVideoFileFPS(path) | ||
466 | expect(fps).to.be.equal(59) | ||
467 | } | ||
468 | } | ||
469 | }) | ||
470 | |||
471 | it('Should provide valid ffprobe data', async function () { | ||
472 | this.timeout(160000) | ||
473 | |||
474 | const videoUUID = (await uploadVideoAndGetId({ server: servers[1], videoName: 'ffprobe data' })).uuid | ||
475 | await waitJobs(servers) | ||
476 | |||
477 | { | ||
478 | const path = join(root(), 'test' + servers[1].internalServerNumber, 'videos', videoUUID + '-240.mp4') | ||
479 | const metadata = await getMetadataFromFile<VideoFileMetadata>(path) | ||
480 | |||
481 | // expected format properties | ||
482 | for (const p of [ | ||
483 | 'tags.encoder', | ||
484 | 'format_long_name', | ||
485 | 'size', | ||
486 | 'bit_rate' | ||
487 | ]) { | ||
488 | expect(metadata.format).to.have.nested.property(p) | ||
489 | } | ||
490 | |||
491 | // expected stream properties | ||
492 | for (const p of [ | ||
493 | 'codec_long_name', | ||
494 | 'profile', | ||
495 | 'width', | ||
496 | 'height', | ||
497 | 'display_aspect_ratio', | ||
498 | 'avg_frame_rate', | ||
499 | 'pix_fmt' | ||
500 | ]) { | ||
501 | expect(metadata.streams[0]).to.have.nested.property(p) | ||
502 | } | ||
503 | |||
504 | expect(metadata).to.not.have.nested.property('format.filename') | ||
505 | } | ||
506 | |||
507 | for (const server of servers) { | ||
508 | const res2 = await getVideo(server.url, videoUUID) | ||
509 | const videoDetails: VideoDetails = res2.body | ||
510 | |||
511 | const videoFiles = videoDetails.files | ||
512 | .concat(videoDetails.streamingPlaylists[0].files) | ||
513 | expect(videoFiles).to.have.lengthOf(8) | ||
514 | |||
515 | for (const file of videoFiles) { | ||
516 | expect(file.metadata).to.be.undefined | ||
517 | expect(file.metadataUrl).to.exist | ||
518 | expect(file.metadataUrl).to.contain(servers[1].url) | ||
519 | expect(file.metadataUrl).to.contain(videoUUID) | ||
520 | |||
521 | const res3 = await getVideoFileMetadataUrl(file.metadataUrl) | ||
522 | const metadata: FfprobeData = res3.body | ||
523 | expect(metadata).to.have.nested.property('format.size') | ||
524 | } | ||
525 | } | ||
526 | }) | ||
527 | |||
419 | after(async function () { | 528 | after(async function () { |
420 | await cleanupTests(servers) | 529 | await cleanupTests(servers) |
421 | }) | 530 | }) |
diff --git a/server/tests/api/videos/videos-filter.ts b/server/tests/api/videos/videos-filter.ts index e1e65260f..95e12e43c 100644 --- a/server/tests/api/videos/videos-filter.ts +++ b/server/tests/api/videos/videos-filter.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -7,8 +7,6 @@ import { | |||
7 | createUser, | 7 | createUser, |
8 | doubleFollow, | 8 | doubleFollow, |
9 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
10 | flushTests, | ||
11 | killallServers, | ||
12 | makeGetRequest, | 10 | makeGetRequest, |
13 | ServerInfo, | 11 | ServerInfo, |
14 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
@@ -98,7 +96,7 @@ describe('Test videos filter validator', function () { | |||
98 | const namesResults = await getVideosNames(server, server.accessToken, 'local') | 96 | const namesResults = await getVideosNames(server, server.accessToken, 'local') |
99 | for (const names of namesResults) { | 97 | for (const names of namesResults) { |
100 | expect(names).to.have.lengthOf(1) | 98 | expect(names).to.have.lengthOf(1) |
101 | expect(names[ 0 ]).to.equal('public ' + server.serverNumber) | 99 | expect(names[0]).to.equal('public ' + server.serverNumber) |
102 | } | 100 | } |
103 | } | 101 | } |
104 | }) | 102 | }) |
@@ -111,9 +109,9 @@ describe('Test videos filter validator', function () { | |||
111 | for (const names of namesResults) { | 109 | for (const names of namesResults) { |
112 | expect(names).to.have.lengthOf(3) | 110 | expect(names).to.have.lengthOf(3) |
113 | 111 | ||
114 | expect(names[ 0 ]).to.equal('public ' + server.serverNumber) | 112 | expect(names[0]).to.equal('public ' + server.serverNumber) |
115 | expect(names[ 1 ]).to.equal('unlisted ' + server.serverNumber) | 113 | expect(names[1]).to.equal('unlisted ' + server.serverNumber) |
116 | expect(names[ 2 ]).to.equal('private ' + server.serverNumber) | 114 | expect(names[2]).to.equal('private ' + server.serverNumber) |
117 | } | 115 | } |
118 | } | 116 | } |
119 | } | 117 | } |
diff --git a/server/tests/api/videos/videos-history.ts b/server/tests/api/videos/videos-history.ts index c7e55c1ab..6f90e9a57 100644 --- a/server/tests/api/videos/videos-history.ts +++ b/server/tests/api/videos/videos-history.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
diff --git a/server/tests/api/videos/videos-overview.ts b/server/tests/api/videos/videos-overview.ts index 975a5c87a..d38bcb6eb 100644 --- a/server/tests/api/videos/videos-overview.ts +++ b/server/tests/api/videos/videos-overview.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { cleanupTests, flushAndRunServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../../../shared/extra-utils' | 5 | import { cleanupTests, flushAndRunServer, ServerInfo, setAccessTokensToServers, uploadVideo, wait } from '../../../../shared/extra-utils' |
6 | import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews' | 6 | import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews' |
7 | import { VideosOverview } from '../../../../shared/models/overviews' | 7 | import { VideosOverview } from '../../../../shared/models/overviews' |
8 | 8 | ||
@@ -20,7 +20,7 @@ describe('Test a videos overview', function () { | |||
20 | }) | 20 | }) |
21 | 21 | ||
22 | it('Should send empty overview', async function () { | 22 | it('Should send empty overview', async function () { |
23 | const res = await getVideosOverview(server.url) | 23 | const res = await getVideosOverview(server.url, 1) |
24 | 24 | ||
25 | const overview: VideosOverview = res.body | 25 | const overview: VideosOverview = res.body |
26 | expect(overview.tags).to.have.lengthOf(0) | 26 | expect(overview.tags).to.have.lengthOf(0) |
@@ -31,15 +31,15 @@ describe('Test a videos overview', function () { | |||
31 | it('Should upload 5 videos in a specific category, tag and channel but not include them in overview', async function () { | 31 | it('Should upload 5 videos in a specific category, tag and channel but not include them in overview', async function () { |
32 | this.timeout(15000) | 32 | this.timeout(15000) |
33 | 33 | ||
34 | for (let i = 0; i < 5; i++) { | 34 | await wait(3000) |
35 | await uploadVideo(server.url, server.accessToken, { | 35 | |
36 | name: 'video ' + i, | 36 | await uploadVideo(server.url, server.accessToken, { |
37 | category: 3, | 37 | name: 'video 0', |
38 | tags: [ 'coucou1', 'coucou2' ] | 38 | category: 3, |
39 | }) | 39 | tags: [ 'coucou1', 'coucou2' ] |
40 | } | 40 | }) |
41 | 41 | ||
42 | const res = await getVideosOverview(server.url) | 42 | const res = await getVideosOverview(server.url, 1) |
43 | 43 | ||
44 | const overview: VideosOverview = res.body | 44 | const overview: VideosOverview = res.body |
45 | expect(overview.tags).to.have.lengthOf(0) | 45 | expect(overview.tags).to.have.lengthOf(0) |
@@ -48,27 +48,55 @@ describe('Test a videos overview', function () { | |||
48 | }) | 48 | }) |
49 | 49 | ||
50 | it('Should upload another video and include all videos in the overview', async function () { | 50 | it('Should upload another video and include all videos in the overview', async function () { |
51 | await uploadVideo(server.url, server.accessToken, { | 51 | this.timeout(15000) |
52 | name: 'video 5', | ||
53 | category: 3, | ||
54 | tags: [ 'coucou1', 'coucou2' ] | ||
55 | }) | ||
56 | 52 | ||
57 | const res = await getVideosOverview(server.url) | 53 | for (let i = 1; i < 6; i++) { |
54 | await uploadVideo(server.url, server.accessToken, { | ||
55 | name: 'video ' + i, | ||
56 | category: 3, | ||
57 | tags: [ 'coucou1', 'coucou2' ] | ||
58 | }) | ||
59 | } | ||
58 | 60 | ||
59 | const overview: VideosOverview = res.body | 61 | await wait(3000) |
60 | expect(overview.tags).to.have.lengthOf(2) | 62 | |
61 | expect(overview.categories).to.have.lengthOf(1) | 63 | { |
62 | expect(overview.channels).to.have.lengthOf(1) | 64 | const res = await getVideosOverview(server.url, 1) |
65 | |||
66 | const overview: VideosOverview = res.body | ||
67 | expect(overview.tags).to.have.lengthOf(1) | ||
68 | expect(overview.categories).to.have.lengthOf(1) | ||
69 | expect(overview.channels).to.have.lengthOf(1) | ||
70 | } | ||
71 | |||
72 | { | ||
73 | const res = await getVideosOverview(server.url, 2) | ||
74 | |||
75 | const overview: VideosOverview = res.body | ||
76 | expect(overview.tags).to.have.lengthOf(1) | ||
77 | expect(overview.categories).to.have.lengthOf(0) | ||
78 | expect(overview.channels).to.have.lengthOf(0) | ||
79 | } | ||
63 | }) | 80 | }) |
64 | 81 | ||
65 | it('Should have the correct overview', async function () { | 82 | it('Should have the correct overview', async function () { |
66 | const res = await getVideosOverview(server.url) | 83 | const res1 = await getVideosOverview(server.url, 1) |
84 | const res2 = await getVideosOverview(server.url, 2) | ||
67 | 85 | ||
68 | const overview: VideosOverview = res.body | 86 | const overview1: VideosOverview = res1.body |
87 | const overview2: VideosOverview = res2.body | ||
88 | |||
89 | const tmp = [ | ||
90 | overview1.tags, | ||
91 | overview1.categories, | ||
92 | overview1.channels, | ||
93 | overview2.tags | ||
94 | ] | ||
95 | |||
96 | for (const arr of tmp) { | ||
97 | expect(arr).to.have.lengthOf(1) | ||
69 | 98 | ||
70 | for (const attr of [ 'tags', 'categories', 'channels' ]) { | 99 | const obj = arr[0] |
71 | const obj = overview[attr][0] | ||
72 | 100 | ||
73 | expect(obj.videos).to.have.lengthOf(6) | 101 | expect(obj.videos).to.have.lengthOf(6) |
74 | expect(obj.videos[0].name).to.equal('video 5') | 102 | expect(obj.videos[0].name).to.equal('video 5') |
@@ -79,12 +107,13 @@ describe('Test a videos overview', function () { | |||
79 | expect(obj.videos[5].name).to.equal('video 0') | 107 | expect(obj.videos[5].name).to.equal('video 0') |
80 | } | 108 | } |
81 | 109 | ||
82 | expect(overview.tags.find(t => t.tag === 'coucou1')).to.not.be.undefined | 110 | const tags = [ overview1.tags[0].tag, overview2.tags[0].tag ] |
83 | expect(overview.tags.find(t => t.tag === 'coucou2')).to.not.be.undefined | 111 | expect(tags.find(t => t === 'coucou1')).to.not.be.undefined |
112 | expect(tags.find(t => t === 'coucou2')).to.not.be.undefined | ||
84 | 113 | ||
85 | expect(overview.categories[0].category.id).to.equal(3) | 114 | expect(overview1.categories[0].category.id).to.equal(3) |
86 | 115 | ||
87 | expect(overview.channels[0].channel.name).to.equal('root_channel') | 116 | expect(overview1.channels[0].channel.name).to.equal('root_channel') |
88 | }) | 117 | }) |
89 | 118 | ||
90 | after(async function () { | 119 | after(async function () { |
diff --git a/server/tests/api/videos/videos-views-cleaner.ts b/server/tests/api/videos/videos-views-cleaner.ts index fbddd40f4..d063d7973 100644 --- a/server/tests/api/videos/videos-views-cleaner.ts +++ b/server/tests/api/videos/videos-views-cleaner.ts | |||
@@ -1,20 +1,22 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | cleanupTests, | ||
7 | closeAllSequelize, | ||
8 | countVideoViewsOf, | ||
9 | doubleFollow, | ||
6 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
7 | flushTests, | ||
8 | killallServers, | 11 | killallServers, |
9 | reRunServer, | 12 | reRunServer, |
10 | flushAndRunServer, | ||
11 | ServerInfo, | 13 | ServerInfo, |
12 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
13 | uploadVideo, uploadVideoAndGetId, viewVideo, wait, countVideoViewsOf, doubleFollow, waitJobs, cleanupTests, closeAllSequelize | 15 | uploadVideoAndGetId, |
16 | viewVideo, | ||
17 | wait, | ||
18 | waitJobs | ||
14 | } from '../../../../shared/extra-utils' | 19 | } from '../../../../shared/extra-utils' |
15 | import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews' | ||
16 | import { VideosOverview } from '../../../../shared/models/overviews' | ||
17 | import { listMyVideosHistory } from '../../../../shared/extra-utils/videos/video-history' | ||
18 | 20 | ||
19 | const expect = chai.expect | 21 | const expect = chai.expect |
20 | 22 | ||
diff --git a/server/tests/cli/create-import-video-file-job.ts b/server/tests/cli/create-import-video-file-job.ts index aca3216bb..dac049fe4 100644 --- a/server/tests/cli/create-import-video-file-job.ts +++ b/server/tests/cli/create-import-video-file-job.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
@@ -71,7 +71,7 @@ describe('Test create import video jobs', function () { | |||
71 | const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body | 71 | const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body |
72 | 72 | ||
73 | expect(videoDetail.files).to.have.lengthOf(2) | 73 | expect(videoDetail.files).to.have.lengthOf(2) |
74 | const [originalVideo, transcodedVideo] = videoDetail.files | 74 | const [ originalVideo, transcodedVideo ] = videoDetail.files |
75 | assertVideoProperties(originalVideo, 720, 'webm', 218910) | 75 | assertVideoProperties(originalVideo, 720, 'webm', 218910) |
76 | assertVideoProperties(transcodedVideo, 480, 'webm', 69217) | 76 | assertVideoProperties(transcodedVideo, 480, 'webm', 69217) |
77 | 77 | ||
@@ -95,7 +95,7 @@ describe('Test create import video jobs', function () { | |||
95 | const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body | 95 | const videoDetail: VideoDetails = (await getVideo(server.url, video.uuid)).body |
96 | 96 | ||
97 | expect(videoDetail.files).to.have.lengthOf(4) | 97 | expect(videoDetail.files).to.have.lengthOf(4) |
98 | const [originalVideo, transcodedVideo420, transcodedVideo320, transcodedVideo240] = videoDetail.files | 98 | const [ originalVideo, transcodedVideo420, transcodedVideo320, transcodedVideo240 ] = videoDetail.files |
99 | assertVideoProperties(originalVideo, 720, 'ogv', 140849) | 99 | assertVideoProperties(originalVideo, 720, 'ogv', 140849) |
100 | assertVideoProperties(transcodedVideo420, 480, 'mp4') | 100 | assertVideoProperties(transcodedVideo420, 480, 'mp4') |
101 | assertVideoProperties(transcodedVideo320, 360, 'mp4') | 101 | assertVideoProperties(transcodedVideo320, 360, 'mp4') |
diff --git a/server/tests/cli/create-transcoding-job.ts b/server/tests/cli/create-transcoding-job.ts index 7897ff1b3..997a9a1fd 100644 --- a/server/tests/cli/create-transcoding-job.ts +++ b/server/tests/cli/create-transcoding-job.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
@@ -8,14 +8,13 @@ import { | |||
8 | doubleFollow, | 8 | doubleFollow, |
9 | execCLI, | 9 | execCLI, |
10 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
11 | flushTests, | ||
12 | getEnvCli, | 11 | getEnvCli, |
13 | getVideo, | 12 | getVideo, |
14 | getVideosList, | 13 | getVideosList, |
15 | killallServers, | ||
16 | ServerInfo, | 14 | ServerInfo, |
17 | setAccessTokensToServers, updateCustomSubConfig, | 15 | setAccessTokensToServers, |
18 | uploadVideo, wait | 16 | updateCustomSubConfig, |
17 | uploadVideo | ||
19 | } from '../../../shared/extra-utils' | 18 | } from '../../../shared/extra-utils' |
20 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' | 19 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
21 | 20 | ||
@@ -23,7 +22,7 @@ const expect = chai.expect | |||
23 | 22 | ||
24 | describe('Test create transcoding jobs', function () { | 23 | describe('Test create transcoding jobs', function () { |
25 | let servers: ServerInfo[] = [] | 24 | let servers: ServerInfo[] = [] |
26 | let videosUUID: string[] = [] | 25 | const videosUUID: string[] = [] |
27 | 26 | ||
28 | const config = { | 27 | const config = { |
29 | transcoding: { | 28 | transcoding: { |
@@ -54,7 +53,7 @@ describe('Test create transcoding jobs', function () { | |||
54 | await doubleFollow(servers[0], servers[1]) | 53 | await doubleFollow(servers[0], servers[1]) |
55 | 54 | ||
56 | for (let i = 1; i <= 5; i++) { | 55 | for (let i = 1; i <= 5; i++) { |
57 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video' + i }) | 56 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' + i }) |
58 | videosUUID.push(res.body.video.uuid) | 57 | videosUUID.push(res.body.video.uuid) |
59 | } | 58 | } |
60 | 59 | ||
@@ -90,7 +89,7 @@ describe('Test create transcoding jobs', function () { | |||
90 | const res = await getVideosList(server.url) | 89 | const res = await getVideosList(server.url) |
91 | const videos = res.body.data | 90 | const videos = res.body.data |
92 | 91 | ||
93 | let infoHashes: { [ id: number ]: string } | 92 | let infoHashes: { [id: number]: string } |
94 | 93 | ||
95 | for (const video of videos) { | 94 | for (const video of videos) { |
96 | const res2 = await getVideo(server.url, video.uuid) | 95 | const res2 = await getVideo(server.url, video.uuid) |
diff --git a/server/tests/cli/optimize-old-videos.ts b/server/tests/cli/optimize-old-videos.ts index de5d672f5..e2e13598f 100644 --- a/server/tests/cli/optimize-old-videos.ts +++ b/server/tests/cli/optimize-old-videos.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
@@ -28,7 +28,9 @@ const expect = chai.expect | |||
28 | 28 | ||
29 | describe('Test optimize old videos', function () { | 29 | describe('Test optimize old videos', function () { |
30 | let servers: ServerInfo[] = [] | 30 | let servers: ServerInfo[] = [] |
31 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
31 | let video1UUID: string | 32 | let video1UUID: string |
33 | // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
32 | let video2UUID: string | 34 | let video2UUID: string |
33 | 35 | ||
34 | before(async function () { | 36 | before(async function () { |
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts index b8c0b1f79..27fbde02d 100644 --- a/server/tests/cli/peertube.ts +++ b/server/tests/cli/peertube.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { expect } from 'chai' | 4 | import { expect } from 'chai' |
@@ -7,14 +7,17 @@ import { | |||
7 | buildAbsoluteFixturePath, | 7 | buildAbsoluteFixturePath, |
8 | cleanupTests, | 8 | cleanupTests, |
9 | createUser, | 9 | createUser, |
10 | doubleFollow, | ||
10 | execCLI, | 11 | execCLI, |
11 | flushAndRunServer, | 12 | flushAndRunServer, |
12 | getEnvCli, | 13 | getEnvCli, |
14 | getLocalIdByUUID, | ||
13 | getVideo, | 15 | getVideo, |
14 | getVideosList, | 16 | getVideosList, |
15 | getVideosListWithToken, removeVideo, | 17 | removeVideo, |
16 | ServerInfo, | 18 | ServerInfo, |
17 | setAccessTokensToServers, | 19 | setAccessTokensToServers, |
20 | uploadVideoAndGetId, | ||
18 | userLogin, | 21 | userLogin, |
19 | waitJobs | 22 | waitJobs |
20 | } from '../../../shared/extra-utils' | 23 | } from '../../../shared/extra-utils' |
@@ -101,7 +104,7 @@ describe('Test CLI wrapper', function () { | |||
101 | 104 | ||
102 | const videos: Video[] = res.body.data | 105 | const videos: Video[] = res.body.data |
103 | 106 | ||
104 | const video: VideoDetails = (await getVideo(server.url, videos[ 0 ].uuid)).body | 107 | const video: VideoDetails = (await getVideo(server.url, videos[0].uuid)).body |
105 | 108 | ||
106 | expect(video.name).to.equal('test upload') | 109 | expect(video.name).to.equal('test upload') |
107 | expect(video.support).to.equal('support_text') | 110 | expect(video.support).to.equal('support_text') |
@@ -210,6 +213,81 @@ describe('Test CLI wrapper', function () { | |||
210 | }) | 213 | }) |
211 | }) | 214 | }) |
212 | 215 | ||
216 | describe('Manage video redundancies', function () { | ||
217 | let anotherServer: ServerInfo | ||
218 | let video1Server2: number | ||
219 | let servers: ServerInfo[] | ||
220 | |||
221 | before(async function () { | ||
222 | this.timeout(120000) | ||
223 | |||
224 | anotherServer = await flushAndRunServer(2) | ||
225 | await setAccessTokensToServers([ anotherServer ]) | ||
226 | |||
227 | await doubleFollow(server, anotherServer) | ||
228 | |||
229 | servers = [ server, anotherServer ] | ||
230 | await waitJobs(servers) | ||
231 | |||
232 | const uuid = (await uploadVideoAndGetId({ server: anotherServer, videoName: 'super video' })).uuid | ||
233 | await waitJobs(servers) | ||
234 | |||
235 | video1Server2 = await getLocalIdByUUID(server.url, uuid) | ||
236 | }) | ||
237 | |||
238 | it('Should add a redundancy', async function () { | ||
239 | this.timeout(60000) | ||
240 | |||
241 | const env = getEnvCli(server) | ||
242 | |||
243 | const params = `add --video ${video1Server2}` | ||
244 | |||
245 | await execCLI(`${env} ${cmd} redundancy ${params}`) | ||
246 | |||
247 | await waitJobs(servers) | ||
248 | }) | ||
249 | |||
250 | it('Should list redundancies', async function () { | ||
251 | this.timeout(60000) | ||
252 | |||
253 | { | ||
254 | const env = getEnvCli(server) | ||
255 | |||
256 | const params = 'list-my-redundancies' | ||
257 | const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`) | ||
258 | |||
259 | expect(stdout).to.contain('super video') | ||
260 | expect(stdout).to.contain(`localhost:${server.port}`) | ||
261 | } | ||
262 | }) | ||
263 | |||
264 | it('Should remove a redundancy', async function () { | ||
265 | this.timeout(60000) | ||
266 | |||
267 | const env = getEnvCli(server) | ||
268 | |||
269 | const params = `remove --video ${video1Server2}` | ||
270 | |||
271 | await execCLI(`${env} ${cmd} redundancy ${params}`) | ||
272 | |||
273 | await waitJobs(servers) | ||
274 | |||
275 | { | ||
276 | const env = getEnvCli(server) | ||
277 | const params = 'list-my-redundancies' | ||
278 | const stdout = await execCLI(`${env} ${cmd} redundancy ${params}`) | ||
279 | |||
280 | expect(stdout).to.not.contain('super video') | ||
281 | } | ||
282 | }) | ||
283 | |||
284 | after(async function () { | ||
285 | this.timeout(10000) | ||
286 | |||
287 | await cleanupTests([ anotherServer ]) | ||
288 | }) | ||
289 | }) | ||
290 | |||
213 | after(async function () { | 291 | after(async function () { |
214 | this.timeout(10000) | 292 | this.timeout(10000) |
215 | 293 | ||
diff --git a/server/tests/cli/plugins.ts b/server/tests/cli/plugins.ts index a5257d671..7f19f14b7 100644 --- a/server/tests/cli/plugins.ts +++ b/server/tests/cli/plugins.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { | 4 | import { |
diff --git a/server/tests/cli/prune-storage.ts b/server/tests/cli/prune-storage.ts index 144e67c44..6cda80070 100644 --- a/server/tests/cli/prune-storage.ts +++ b/server/tests/cli/prune-storage.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
@@ -11,18 +11,19 @@ import { | |||
11 | execCLI, | 11 | execCLI, |
12 | flushAndRunMultipleServers, | 12 | flushAndRunMultipleServers, |
13 | getAccount, | 13 | getAccount, |
14 | getEnvCli, makeGetRequest, makeRawRequest, | 14 | getEnvCli, |
15 | makeGetRequest, | ||
15 | ServerInfo, | 16 | ServerInfo, |
16 | setAccessTokensToServers, setDefaultVideoChannel, | 17 | setAccessTokensToServers, |
18 | setDefaultVideoChannel, | ||
17 | updateMyAvatar, | 19 | updateMyAvatar, |
18 | uploadVideo, | 20 | uploadVideo, |
19 | wait | 21 | wait |
20 | } from '../../../shared/extra-utils' | 22 | } from '../../../shared/extra-utils' |
21 | import { Account, VideoPlaylistPrivacy } from '../../../shared/models' | 23 | import { Account, VideoPlaylistPrivacy } from '../../../shared/models' |
22 | import { createFile, readdir } from 'fs-extra' | 24 | import { createFile, readdir } from 'fs-extra' |
23 | import * as uuidv4 from 'uuid/v4' | 25 | import { v4 as uuidv4 } from 'uuid' |
24 | import { join } from 'path' | 26 | import { join } from 'path' |
25 | import * as request from 'supertest' | ||
26 | 27 | ||
27 | const expect = chai.expect | 28 | const expect = chai.expect |
28 | 29 | ||
@@ -61,7 +62,7 @@ async function assertCountAreOkay (servers: ServerInfo[]) { | |||
61 | 62 | ||
62 | describe('Test prune storage scripts', function () { | 63 | describe('Test prune storage scripts', function () { |
63 | let servers: ServerInfo[] | 64 | let servers: ServerInfo[] |
64 | const badNames: { [ directory: string ]: string[] } = {} | 65 | const badNames: { [directory: string]: string[] } = {} |
65 | 66 | ||
66 | before(async function () { | 67 | before(async function () { |
67 | this.timeout(120000) | 68 | this.timeout(120000) |
@@ -92,20 +93,20 @@ describe('Test prune storage scripts', function () { | |||
92 | 93 | ||
93 | // Lazy load the remote avatar | 94 | // Lazy load the remote avatar |
94 | { | 95 | { |
95 | const res = await getAccount(servers[ 0 ].url, 'root@localhost:' + servers[ 1 ].port) | 96 | const res = await getAccount(servers[0].url, 'root@localhost:' + servers[1].port) |
96 | const account: Account = res.body | 97 | const account: Account = res.body |
97 | await makeGetRequest({ | 98 | await makeGetRequest({ |
98 | url: servers[ 0 ].url, | 99 | url: servers[0].url, |
99 | path: account.avatar.path, | 100 | path: account.avatar.path, |
100 | statusCodeExpected: 200 | 101 | statusCodeExpected: 200 |
101 | }) | 102 | }) |
102 | } | 103 | } |
103 | 104 | ||
104 | { | 105 | { |
105 | const res = await getAccount(servers[ 1 ].url, 'root@localhost:' + servers[ 0 ].port) | 106 | const res = await getAccount(servers[1].url, 'root@localhost:' + servers[0].port) |
106 | const account: Account = res.body | 107 | const account: Account = res.body |
107 | await makeGetRequest({ | 108 | await makeGetRequest({ |
108 | url: servers[ 1 ].url, | 109 | url: servers[1].url, |
109 | path: account.avatar.path, | 110 | path: account.avatar.path, |
110 | statusCodeExpected: 200 | 111 | statusCodeExpected: 200 |
111 | }) | 112 | }) |
diff --git a/server/tests/cli/update-host.ts b/server/tests/cli/update-host.ts index 55c43b32f..2070f16f5 100644 --- a/server/tests/cli/update-host.ts +++ b/server/tests/cli/update-host.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
diff --git a/server/tests/client.ts b/server/tests/client.ts index 778dcd08e..670bc6701 100644 --- a/server/tests/client.ts +++ b/server/tests/client.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
@@ -112,8 +112,7 @@ describe('Test a client controllers', function () { | |||
112 | it('Should have valid index html tags (title, description...)', async function () { | 112 | it('Should have valid index html tags (title, description...)', async function () { |
113 | const res = await makeHTMLRequest(server.url, '/videos/trending') | 113 | const res = await makeHTMLRequest(server.url, '/videos/trending') |
114 | 114 | ||
115 | const description = 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser ' + | 115 | const description = 'PeerTube, an ActivityPub-federated video streaming platform using P2P directly in your web browser.' |
116 | 'with WebTorrent and Angular.' | ||
117 | checkIndexTags(res.text, 'PeerTube', description, '') | 116 | checkIndexTags(res.text, 'PeerTube', description, '') |
118 | }) | 117 | }) |
119 | 118 | ||
diff --git a/server/tests/external-plugins/auth-ldap.ts b/server/tests/external-plugins/auth-ldap.ts new file mode 100644 index 000000000..0f0a08532 --- /dev/null +++ b/server/tests/external-plugins/auth-ldap.ts | |||
@@ -0,0 +1,108 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { expect } from 'chai' | ||
5 | import { User } from '@shared/models/users/user.model' | ||
6 | import { | ||
7 | getMyUserInformation, | ||
8 | installPlugin, | ||
9 | setAccessTokensToServers, | ||
10 | uninstallPlugin, | ||
11 | updatePluginSettings, | ||
12 | uploadVideo, | ||
13 | userLogin | ||
14 | } from '../../../shared/extra-utils' | ||
15 | import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' | ||
16 | |||
17 | describe('Official plugin auth-ldap', function () { | ||
18 | let server: ServerInfo | ||
19 | let accessToken: string | ||
20 | |||
21 | before(async function () { | ||
22 | this.timeout(30000) | ||
23 | |||
24 | server = await flushAndRunServer(1) | ||
25 | await setAccessTokensToServers([ server ]) | ||
26 | |||
27 | await installPlugin({ | ||
28 | url: server.url, | ||
29 | accessToken: server.accessToken, | ||
30 | npmName: 'peertube-plugin-auth-ldap' | ||
31 | }) | ||
32 | }) | ||
33 | |||
34 | it('Should not login with without LDAP settings', async function () { | ||
35 | await userLogin(server, { username: 'fry', password: 'fry' }, 400) | ||
36 | }) | ||
37 | |||
38 | it('Should not login with bad LDAP settings', async function () { | ||
39 | await updatePluginSettings({ | ||
40 | url: server.url, | ||
41 | accessToken: server.accessToken, | ||
42 | npmName: 'peertube-plugin-auth-ldap', | ||
43 | settings: { | ||
44 | 'bind-credentials': 'GoodNewsEveryone', | ||
45 | 'bind-dn': 'cn=admin,dc=planetexpress,dc=com', | ||
46 | 'insecure-tls': false, | ||
47 | 'mail-property': 'mail', | ||
48 | 'search-base': 'ou=people,dc=planetexpress,dc=com', | ||
49 | 'search-filter': '(|(mail={{username}})(uid={{username}}))', | ||
50 | 'url': 'ldap://ldap:390', | ||
51 | 'username-property': 'uid' | ||
52 | } | ||
53 | }) | ||
54 | |||
55 | await userLogin(server, { username: 'fry', password: 'fry' }, 400) | ||
56 | }) | ||
57 | |||
58 | it('Should not login with good LDAP settings but wrong username/password', async function () { | ||
59 | await updatePluginSettings({ | ||
60 | url: server.url, | ||
61 | accessToken: server.accessToken, | ||
62 | npmName: 'peertube-plugin-auth-ldap', | ||
63 | settings: { | ||
64 | 'bind-credentials': 'GoodNewsEveryone', | ||
65 | 'bind-dn': 'cn=admin,dc=planetexpress,dc=com', | ||
66 | 'insecure-tls': false, | ||
67 | 'mail-property': 'mail', | ||
68 | 'search-base': 'ou=people,dc=planetexpress,dc=com', | ||
69 | 'search-filter': '(|(mail={{username}})(uid={{username}}))', | ||
70 | 'url': 'ldap://ldap:389', | ||
71 | 'username-property': 'uid' | ||
72 | } | ||
73 | }) | ||
74 | |||
75 | await userLogin(server, { username: 'fry', password: 'bad password' }, 400) | ||
76 | await userLogin(server, { username: 'fryr', password: 'fry' }, 400) | ||
77 | }) | ||
78 | |||
79 | it('Should login with the appropriate username/password', async function () { | ||
80 | accessToken = await userLogin(server, { username: 'fry', password: 'fry' }) | ||
81 | }) | ||
82 | |||
83 | it('Should login with the appropriate email/password', async function () { | ||
84 | accessToken = await userLogin(server, { username: 'fry@planetexpress.com', password: 'fry' }) | ||
85 | }) | ||
86 | |||
87 | it('Should login get my profile', async function () { | ||
88 | const res = await getMyUserInformation(server.url, accessToken) | ||
89 | const body: User = res.body | ||
90 | |||
91 | expect(body.username).to.equal('fry') | ||
92 | expect(body.email).to.equal('fry@planetexpress.com') | ||
93 | }) | ||
94 | |||
95 | it('Should upload a video', async function () { | ||
96 | await uploadVideo(server.url, accessToken, { name: 'my super video' }) | ||
97 | }) | ||
98 | |||
99 | it('Should not login if the plugin is uninstalled', async function () { | ||
100 | await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-auth-ldap' }) | ||
101 | |||
102 | await userLogin(server, { username: 'fry@planetexpress.com', password: 'fry' }, 400) | ||
103 | }) | ||
104 | |||
105 | after(async function () { | ||
106 | await cleanupTests([ server ]) | ||
107 | }) | ||
108 | }) | ||
diff --git a/server/tests/external-plugins/auto-mute.ts b/server/tests/external-plugins/auto-mute.ts new file mode 100644 index 000000000..bfdbee80a --- /dev/null +++ b/server/tests/external-plugins/auto-mute.ts | |||
@@ -0,0 +1,243 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { expect } from 'chai' | ||
5 | import { | ||
6 | addAccountToServerBlocklist, | ||
7 | addServerToAccountBlocklist, | ||
8 | removeAccountFromServerBlocklist | ||
9 | } from '@shared/extra-utils/users/blocklist' | ||
10 | import { | ||
11 | doubleFollow, | ||
12 | getVideosList, | ||
13 | installPlugin, | ||
14 | makeGetRequest, | ||
15 | MockBlocklist, | ||
16 | setAccessTokensToServers, | ||
17 | updatePluginSettings, | ||
18 | uploadVideoAndGetId, | ||
19 | wait | ||
20 | } from '../../../shared/extra-utils' | ||
21 | import { | ||
22 | cleanupTests, | ||
23 | flushAndRunMultipleServers, | ||
24 | killallServers, | ||
25 | reRunServer, | ||
26 | ServerInfo | ||
27 | } from '../../../shared/extra-utils/server/servers' | ||
28 | |||
29 | describe('Official plugin auto-mute', function () { | ||
30 | const autoMuteListPath = '/plugins/auto-mute/router/api/v1/mute-list' | ||
31 | let servers: ServerInfo[] | ||
32 | let blocklistServer: MockBlocklist | ||
33 | |||
34 | before(async function () { | ||
35 | this.timeout(30000) | ||
36 | |||
37 | servers = await flushAndRunMultipleServers(2) | ||
38 | await setAccessTokensToServers(servers) | ||
39 | |||
40 | for (const server of servers) { | ||
41 | await installPlugin({ | ||
42 | url: server.url, | ||
43 | accessToken: server.accessToken, | ||
44 | npmName: 'peertube-plugin-auto-mute' | ||
45 | }) | ||
46 | } | ||
47 | |||
48 | blocklistServer = new MockBlocklist() | ||
49 | await blocklistServer.initialize() | ||
50 | |||
51 | await uploadVideoAndGetId({ server: servers[0], videoName: 'video server 1' }) | ||
52 | await uploadVideoAndGetId({ server: servers[1], videoName: 'video server 2' }) | ||
53 | |||
54 | await doubleFollow(servers[0], servers[1]) | ||
55 | }) | ||
56 | |||
57 | it('Should update plugin settings', async function () { | ||
58 | await updatePluginSettings({ | ||
59 | url: servers[0].url, | ||
60 | accessToken: servers[0].accessToken, | ||
61 | npmName: 'peertube-plugin-auto-mute', | ||
62 | settings: { | ||
63 | 'blocklist-urls': 'http://localhost:42100/blocklist', | ||
64 | 'check-seconds-interval': 1 | ||
65 | } | ||
66 | }) | ||
67 | }) | ||
68 | |||
69 | it('Should add a server blocklist', async function () { | ||
70 | this.timeout(10000) | ||
71 | |||
72 | blocklistServer.replace({ | ||
73 | data: [ | ||
74 | { | ||
75 | value: 'localhost:' + servers[1].port | ||
76 | } | ||
77 | ] | ||
78 | }) | ||
79 | |||
80 | await wait(2000) | ||
81 | |||
82 | const res = await getVideosList(servers[0].url) | ||
83 | expect(res.body.total).to.equal(1) | ||
84 | }) | ||
85 | |||
86 | it('Should remove a server blocklist', async function () { | ||
87 | this.timeout(10000) | ||
88 | |||
89 | blocklistServer.replace({ | ||
90 | data: [ | ||
91 | { | ||
92 | value: 'localhost:' + servers[1].port, | ||
93 | action: 'remove' | ||
94 | } | ||
95 | ] | ||
96 | }) | ||
97 | |||
98 | await wait(2000) | ||
99 | |||
100 | const res = await getVideosList(servers[0].url) | ||
101 | expect(res.body.total).to.equal(2) | ||
102 | }) | ||
103 | |||
104 | it('Should add an account blocklist', async function () { | ||
105 | this.timeout(10000) | ||
106 | |||
107 | blocklistServer.replace({ | ||
108 | data: [ | ||
109 | { | ||
110 | value: 'root@localhost:' + servers[1].port | ||
111 | } | ||
112 | ] | ||
113 | }) | ||
114 | |||
115 | await wait(2000) | ||
116 | |||
117 | const res = await getVideosList(servers[0].url) | ||
118 | expect(res.body.total).to.equal(1) | ||
119 | }) | ||
120 | |||
121 | it('Should remove an account blocklist', async function () { | ||
122 | this.timeout(10000) | ||
123 | |||
124 | blocklistServer.replace({ | ||
125 | data: [ | ||
126 | { | ||
127 | value: 'root@localhost:' + servers[1].port, | ||
128 | action: 'remove' | ||
129 | } | ||
130 | ] | ||
131 | }) | ||
132 | |||
133 | await wait(2000) | ||
134 | |||
135 | const res = await getVideosList(servers[0].url) | ||
136 | expect(res.body.total).to.equal(2) | ||
137 | }) | ||
138 | |||
139 | it('Should auto mute an account, manually unmute it and do not remute it automatically', async function () { | ||
140 | this.timeout(20000) | ||
141 | |||
142 | const account = 'root@localhost:' + servers[1].port | ||
143 | |||
144 | blocklistServer.replace({ | ||
145 | data: [ | ||
146 | { | ||
147 | value: account, | ||
148 | updatedAt: new Date().toISOString() | ||
149 | } | ||
150 | ] | ||
151 | }) | ||
152 | |||
153 | await wait(2000) | ||
154 | |||
155 | { | ||
156 | const res = await getVideosList(servers[0].url) | ||
157 | expect(res.body.total).to.equal(1) | ||
158 | } | ||
159 | |||
160 | await removeAccountFromServerBlocklist(servers[0].url, servers[0].accessToken, account) | ||
161 | |||
162 | { | ||
163 | const res = await getVideosList(servers[0].url) | ||
164 | expect(res.body.total).to.equal(2) | ||
165 | } | ||
166 | |||
167 | killallServers([ servers[0] ]) | ||
168 | await reRunServer(servers[0]) | ||
169 | await wait(2000) | ||
170 | |||
171 | { | ||
172 | const res = await getVideosList(servers[0].url) | ||
173 | expect(res.body.total).to.equal(2) | ||
174 | } | ||
175 | }) | ||
176 | |||
177 | it('Should not expose the auto mute list', async function () { | ||
178 | await makeGetRequest({ | ||
179 | url: servers[0].url, | ||
180 | path: '/plugins/auto-mute/router/api/v1/mute-list', | ||
181 | statusCodeExpected: 403 | ||
182 | }) | ||
183 | }) | ||
184 | |||
185 | it('Should enable auto mute list', async function () { | ||
186 | await updatePluginSettings({ | ||
187 | url: servers[0].url, | ||
188 | accessToken: servers[0].accessToken, | ||
189 | npmName: 'peertube-plugin-auto-mute', | ||
190 | settings: { | ||
191 | 'blocklist-urls': '', | ||
192 | 'check-seconds-interval': 1, | ||
193 | 'expose-mute-list': true | ||
194 | } | ||
195 | }) | ||
196 | |||
197 | await makeGetRequest({ | ||
198 | url: servers[0].url, | ||
199 | path: '/plugins/auto-mute/router/api/v1/mute-list', | ||
200 | statusCodeExpected: 200 | ||
201 | }) | ||
202 | }) | ||
203 | |||
204 | it('Should mute an account on server 1, and server 2 auto mutes it', async function () { | ||
205 | this.timeout(20000) | ||
206 | |||
207 | await updatePluginSettings({ | ||
208 | url: servers[1].url, | ||
209 | accessToken: servers[1].accessToken, | ||
210 | npmName: 'peertube-plugin-auto-mute', | ||
211 | settings: { | ||
212 | 'blocklist-urls': 'http://localhost:' + servers[0].port + autoMuteListPath, | ||
213 | 'check-seconds-interval': 1, | ||
214 | 'expose-mute-list': false | ||
215 | } | ||
216 | }) | ||
217 | |||
218 | await addAccountToServerBlocklist(servers[0].url, servers[0].accessToken, 'root@localhost:' + servers[1].port) | ||
219 | await addServerToAccountBlocklist(servers[0].url, servers[0].accessToken, 'localhost:' + servers[1].port) | ||
220 | |||
221 | const res = await makeGetRequest({ | ||
222 | url: servers[0].url, | ||
223 | path: '/plugins/auto-mute/router/api/v1/mute-list', | ||
224 | statusCodeExpected: 200 | ||
225 | }) | ||
226 | |||
227 | const data = res.body.data | ||
228 | expect(data).to.have.lengthOf(1) | ||
229 | expect(data[0].updatedAt).to.exist | ||
230 | expect(data[0].value).to.equal('root@localhost:' + servers[1].port) | ||
231 | |||
232 | await wait(2000) | ||
233 | |||
234 | for (const server of servers) { | ||
235 | const res = await getVideosList(server.url) | ||
236 | expect(res.body.total).to.equal(1) | ||
237 | } | ||
238 | }) | ||
239 | |||
240 | after(async function () { | ||
241 | await cleanupTests(servers) | ||
242 | }) | ||
243 | }) | ||
diff --git a/server/tests/external-plugins/index.ts b/server/tests/external-plugins/index.ts new file mode 100644 index 000000000..d17894c15 --- /dev/null +++ b/server/tests/external-plugins/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | import './auth-ldap' | ||
2 | import './auto-mute' | ||
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index 437470327..7fac921a3 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -19,6 +19,8 @@ import * as libxmljs from 'libxmljs' | |||
19 | import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' | 19 | import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' |
20 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' | 20 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
21 | import { User } from '../../../shared/models/users' | 21 | import { User } from '../../../shared/models/users' |
22 | import { VideoPrivacy } from '@shared/models' | ||
23 | import { addAccountToServerBlocklist } from '@shared/extra-utils/users/blocklist' | ||
22 | 24 | ||
23 | chai.use(require('chai-xml')) | 25 | chai.use(require('chai-xml')) |
24 | chai.use(require('chai-json-schema')) | 26 | chai.use(require('chai-json-schema')) |
@@ -51,7 +53,7 @@ describe('Test syndication feeds', () => { | |||
51 | 53 | ||
52 | { | 54 | { |
53 | const attr = { username: 'john', password: 'password' } | 55 | const attr = { username: 'john', password: 'password' } |
54 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: attr.username, password: attr.password }) | 56 | await createUser({ url: servers[0].url, accessToken: servers[0].accessToken, username: attr.username, password: attr.password }) |
55 | userAccessToken = await userLogin(servers[0], attr) | 57 | userAccessToken = await userLogin(servers[0], attr) |
56 | 58 | ||
57 | const res = await getMyUserInformation(servers[0].url, userAccessToken) | 59 | const res = await getMyUserInformation(servers[0].url, userAccessToken) |
@@ -61,7 +63,7 @@ describe('Test syndication feeds', () => { | |||
61 | } | 63 | } |
62 | 64 | ||
63 | { | 65 | { |
64 | await uploadVideo(servers[ 0 ].url, userAccessToken, { name: 'user video' }) | 66 | await uploadVideo(servers[0].url, userAccessToken, { name: 'user video' }) |
65 | } | 67 | } |
66 | 68 | ||
67 | { | 69 | { |
@@ -70,11 +72,19 @@ describe('Test syndication feeds', () => { | |||
70 | description: 'my super description for server 1', | 72 | description: 'my super description for server 1', |
71 | fixture: 'video_short.webm' | 73 | fixture: 'video_short.webm' |
72 | } | 74 | } |
73 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoAttributes) | 75 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) |
74 | const videoId = res.body.video.id | 76 | const videoId = res.body.video.id |
75 | 77 | ||
76 | await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, 'super comment 1') | 78 | await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 1') |
77 | await addVideoCommentThread(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, 'super comment 2') | 79 | await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'super comment 2') |
80 | } | ||
81 | |||
82 | { | ||
83 | const videoAttributes = { name: 'unlisted video', privacy: VideoPrivacy.UNLISTED } | ||
84 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, videoAttributes) | ||
85 | const videoId = res.body.video.id | ||
86 | |||
87 | await addVideoCommentThread(servers[0].url, servers[0].accessToken, videoId, 'comment on unlisted video') | ||
78 | } | 88 | } |
79 | 89 | ||
80 | await waitJobs(servers) | 90 | await waitJobs(servers) |
@@ -84,18 +94,18 @@ describe('Test syndication feeds', () => { | |||
84 | 94 | ||
85 | it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () { | 95 | it('Should be well formed XML (covers RSS 2.0 and ATOM 1.0 endpoints)', async function () { |
86 | for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { | 96 | for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { |
87 | const rss = await getXMLfeed(servers[ 0 ].url, feed) | 97 | const rss = await getXMLfeed(servers[0].url, feed) |
88 | expect(rss.text).xml.to.be.valid() | 98 | expect(rss.text).xml.to.be.valid() |
89 | 99 | ||
90 | const atom = await getXMLfeed(servers[ 0 ].url, feed, 'atom') | 100 | const atom = await getXMLfeed(servers[0].url, feed, 'atom') |
91 | expect(atom.text).xml.to.be.valid() | 101 | expect(atom.text).xml.to.be.valid() |
92 | } | 102 | } |
93 | }) | 103 | }) |
94 | 104 | ||
95 | it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () { | 105 | it('Should be well formed JSON (covers JSON feed 1.0 endpoint)', async function () { |
96 | for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { | 106 | for (const feed of [ 'video-comments' as 'video-comments', 'videos' as 'videos' ]) { |
97 | const json = await getJSONfeed(servers[ 0 ].url, feed) | 107 | const json = await getJSONfeed(servers[0].url, feed) |
98 | expect(JSON.parse(json.text)).to.be.jsonSchema({ 'type': 'object' }) | 108 | expect(JSON.parse(json.text)).to.be.jsonSchema({ type: 'object' }) |
99 | } | 109 | } |
100 | }) | 110 | }) |
101 | }) | 111 | }) |
@@ -118,11 +128,11 @@ describe('Test syndication feeds', () => { | |||
118 | const json = await getJSONfeed(server.url, 'videos') | 128 | const json = await getJSONfeed(server.url, 'videos') |
119 | const jsonObj = JSON.parse(json.text) | 129 | const jsonObj = JSON.parse(json.text) |
120 | expect(jsonObj.items.length).to.be.equal(2) | 130 | expect(jsonObj.items.length).to.be.equal(2) |
121 | expect(jsonObj.items[ 0 ].attachments).to.exist | 131 | expect(jsonObj.items[0].attachments).to.exist |
122 | expect(jsonObj.items[ 0 ].attachments.length).to.be.eq(1) | 132 | expect(jsonObj.items[0].attachments.length).to.be.eq(1) |
123 | expect(jsonObj.items[ 0 ].attachments[ 0 ].mime_type).to.be.eq('application/x-bittorrent') | 133 | expect(jsonObj.items[0].attachments[0].mime_type).to.be.eq('application/x-bittorrent') |
124 | expect(jsonObj.items[ 0 ].attachments[ 0 ].size_in_bytes).to.be.eq(218910) | 134 | expect(jsonObj.items[0].attachments[0].size_in_bytes).to.be.eq(218910) |
125 | expect(jsonObj.items[ 0 ].attachments[ 0 ].url).to.contain('720.torrent') | 135 | expect(jsonObj.items[0].attachments[0].url).to.contain('720.torrent') |
126 | } | 136 | } |
127 | }) | 137 | }) |
128 | 138 | ||
@@ -131,16 +141,16 @@ describe('Test syndication feeds', () => { | |||
131 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId }) | 141 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: rootAccountId }) |
132 | const jsonObj = JSON.parse(json.text) | 142 | const jsonObj = JSON.parse(json.text) |
133 | expect(jsonObj.items.length).to.be.equal(1) | 143 | expect(jsonObj.items.length).to.be.equal(1) |
134 | expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') | 144 | expect(jsonObj.items[0].title).to.equal('my super name for server 1') |
135 | expect(jsonObj.items[ 0 ].author.name).to.equal('root') | 145 | expect(jsonObj.items[0].author.name).to.equal('root') |
136 | } | 146 | } |
137 | 147 | ||
138 | { | 148 | { |
139 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId }) | 149 | const json = await getJSONfeed(servers[0].url, 'videos', { accountId: userAccountId }) |
140 | const jsonObj = JSON.parse(json.text) | 150 | const jsonObj = JSON.parse(json.text) |
141 | expect(jsonObj.items.length).to.be.equal(1) | 151 | expect(jsonObj.items.length).to.be.equal(1) |
142 | expect(jsonObj.items[ 0 ].title).to.equal('user video') | 152 | expect(jsonObj.items[0].title).to.equal('user video') |
143 | expect(jsonObj.items[ 0 ].author.name).to.equal('john') | 153 | expect(jsonObj.items[0].author.name).to.equal('john') |
144 | } | 154 | } |
145 | 155 | ||
146 | for (const server of servers) { | 156 | for (const server of servers) { |
@@ -148,14 +158,14 @@ describe('Test syndication feeds', () => { | |||
148 | const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port }) | 158 | const json = await getJSONfeed(server.url, 'videos', { accountName: 'root@localhost:' + servers[0].port }) |
149 | const jsonObj = JSON.parse(json.text) | 159 | const jsonObj = JSON.parse(json.text) |
150 | expect(jsonObj.items.length).to.be.equal(1) | 160 | expect(jsonObj.items.length).to.be.equal(1) |
151 | expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') | 161 | expect(jsonObj.items[0].title).to.equal('my super name for server 1') |
152 | } | 162 | } |
153 | 163 | ||
154 | { | 164 | { |
155 | const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port }) | 165 | const json = await getJSONfeed(server.url, 'videos', { accountName: 'john@localhost:' + servers[0].port }) |
156 | const jsonObj = JSON.parse(json.text) | 166 | const jsonObj = JSON.parse(json.text) |
157 | expect(jsonObj.items.length).to.be.equal(1) | 167 | expect(jsonObj.items.length).to.be.equal(1) |
158 | expect(jsonObj.items[ 0 ].title).to.equal('user video') | 168 | expect(jsonObj.items[0].title).to.equal('user video') |
159 | } | 169 | } |
160 | } | 170 | } |
161 | }) | 171 | }) |
@@ -165,16 +175,16 @@ describe('Test syndication feeds', () => { | |||
165 | const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId }) | 175 | const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: rootChannelId }) |
166 | const jsonObj = JSON.parse(json.text) | 176 | const jsonObj = JSON.parse(json.text) |
167 | expect(jsonObj.items.length).to.be.equal(1) | 177 | expect(jsonObj.items.length).to.be.equal(1) |
168 | expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') | 178 | expect(jsonObj.items[0].title).to.equal('my super name for server 1') |
169 | expect(jsonObj.items[ 0 ].author.name).to.equal('root') | 179 | expect(jsonObj.items[0].author.name).to.equal('root') |
170 | } | 180 | } |
171 | 181 | ||
172 | { | 182 | { |
173 | const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId }) | 183 | const json = await getJSONfeed(servers[0].url, 'videos', { videoChannelId: userChannelId }) |
174 | const jsonObj = JSON.parse(json.text) | 184 | const jsonObj = JSON.parse(json.text) |
175 | expect(jsonObj.items.length).to.be.equal(1) | 185 | expect(jsonObj.items.length).to.be.equal(1) |
176 | expect(jsonObj.items[ 0 ].title).to.equal('user video') | 186 | expect(jsonObj.items[0].title).to.equal('user video') |
177 | expect(jsonObj.items[ 0 ].author.name).to.equal('john') | 187 | expect(jsonObj.items[0].author.name).to.equal('john') |
178 | } | 188 | } |
179 | 189 | ||
180 | for (const server of servers) { | 190 | for (const server of servers) { |
@@ -182,30 +192,42 @@ describe('Test syndication feeds', () => { | |||
182 | const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port }) | 192 | const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'root_channel@localhost:' + servers[0].port }) |
183 | const jsonObj = JSON.parse(json.text) | 193 | const jsonObj = JSON.parse(json.text) |
184 | expect(jsonObj.items.length).to.be.equal(1) | 194 | expect(jsonObj.items.length).to.be.equal(1) |
185 | expect(jsonObj.items[ 0 ].title).to.equal('my super name for server 1') | 195 | expect(jsonObj.items[0].title).to.equal('my super name for server 1') |
186 | } | 196 | } |
187 | 197 | ||
188 | { | 198 | { |
189 | const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port }) | 199 | const json = await getJSONfeed(server.url, 'videos', { videoChannelName: 'john_channel@localhost:' + servers[0].port }) |
190 | const jsonObj = JSON.parse(json.text) | 200 | const jsonObj = JSON.parse(json.text) |
191 | expect(jsonObj.items.length).to.be.equal(1) | 201 | expect(jsonObj.items.length).to.be.equal(1) |
192 | expect(jsonObj.items[ 0 ].title).to.equal('user video') | 202 | expect(jsonObj.items[0].title).to.equal('user video') |
193 | } | 203 | } |
194 | } | 204 | } |
195 | }) | 205 | }) |
196 | }) | 206 | }) |
197 | 207 | ||
198 | describe('Video comments feed', function () { | 208 | describe('Video comments feed', function () { |
199 | it('Should contain valid comments (covers JSON feed 1.0 endpoint)', async function () { | 209 | |
210 | it('Should contain valid comments (covers JSON feed 1.0 endpoint) and not from unlisted videos', async function () { | ||
200 | for (const server of servers) { | 211 | for (const server of servers) { |
201 | const json = await getJSONfeed(server.url, 'video-comments') | 212 | const json = await getJSONfeed(server.url, 'video-comments') |
202 | 213 | ||
203 | const jsonObj = JSON.parse(json.text) | 214 | const jsonObj = JSON.parse(json.text) |
204 | expect(jsonObj.items.length).to.be.equal(2) | 215 | expect(jsonObj.items.length).to.be.equal(2) |
205 | expect(jsonObj.items[ 0 ].html_content).to.equal('super comment 2') | 216 | expect(jsonObj.items[0].html_content).to.equal('super comment 2') |
206 | expect(jsonObj.items[ 1 ].html_content).to.equal('super comment 1') | 217 | expect(jsonObj.items[1].html_content).to.equal('super comment 1') |
207 | } | 218 | } |
208 | }) | 219 | }) |
220 | |||
221 | it('Should not list comments from muted accounts or instances', async function () { | ||
222 | await addAccountToServerBlocklist(servers[1].url, servers[1].accessToken, 'root@localhost:' + servers[0].port) | ||
223 | |||
224 | { | ||
225 | const json = await getJSONfeed(servers[1].url, 'video-comments', { version: 2 }) | ||
226 | const jsonObj = JSON.parse(json.text) | ||
227 | expect(jsonObj.items.length).to.be.equal(0) | ||
228 | } | ||
229 | |||
230 | }) | ||
209 | }) | 231 | }) |
210 | 232 | ||
211 | after(async function () { | 233 | after(async function () { |
diff --git a/server/tests/fixtures/peertube-plugin-test-external-auth-one/main.js b/server/tests/fixtures/peertube-plugin-test-external-auth-one/main.js new file mode 100644 index 000000000..c65b8d3a8 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-external-auth-one/main.js | |||
@@ -0,0 +1,75 @@ | |||
1 | async function register ({ | ||
2 | registerExternalAuth, | ||
3 | peertubeHelpers, | ||
4 | settingsManager, | ||
5 | unregisterExternalAuth | ||
6 | }) { | ||
7 | { | ||
8 | const result = registerExternalAuth({ | ||
9 | authName: 'external-auth-1', | ||
10 | authDisplayName: () => 'External Auth 1', | ||
11 | onLogout: user => peertubeHelpers.logger.info('On logout %s', user.username), | ||
12 | onAuthRequest: (req, res) => { | ||
13 | const username = req.query.username | ||
14 | |||
15 | result.userAuthenticated({ | ||
16 | req, | ||
17 | res, | ||
18 | username, | ||
19 | email: username + '@example.com' | ||
20 | }) | ||
21 | } | ||
22 | }) | ||
23 | } | ||
24 | |||
25 | { | ||
26 | const result = registerExternalAuth({ | ||
27 | authName: 'external-auth-2', | ||
28 | authDisplayName: () => 'External Auth 2', | ||
29 | onAuthRequest: (req, res) => { | ||
30 | result.userAuthenticated({ | ||
31 | req, | ||
32 | res, | ||
33 | username: 'kefka', | ||
34 | email: 'kefka@example.com', | ||
35 | role: 0, | ||
36 | displayName: 'Kefka Palazzo' | ||
37 | }) | ||
38 | }, | ||
39 | hookTokenValidity: (options) => { | ||
40 | if (options.type === 'refresh') { | ||
41 | return { valid: false } | ||
42 | } | ||
43 | |||
44 | if (options.type === 'access') { | ||
45 | const token = options.token | ||
46 | const now = new Date() | ||
47 | now.setTime(now.getTime() - 5000) | ||
48 | |||
49 | const createdAt = new Date(token.createdAt) | ||
50 | |||
51 | return { valid: createdAt.getTime() >= now.getTime() } | ||
52 | } | ||
53 | |||
54 | return { valid: true } | ||
55 | } | ||
56 | }) | ||
57 | } | ||
58 | |||
59 | settingsManager.onSettingsChange(settings => { | ||
60 | if (settings.disableKefka) { | ||
61 | unregisterExternalAuth('external-auth-2') | ||
62 | } | ||
63 | }) | ||
64 | } | ||
65 | |||
66 | async function unregister () { | ||
67 | return | ||
68 | } | ||
69 | |||
70 | module.exports = { | ||
71 | register, | ||
72 | unregister | ||
73 | } | ||
74 | |||
75 | // ########################################################################### | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-external-auth-one/package.json b/server/tests/fixtures/peertube-plugin-test-external-auth-one/package.json new file mode 100644 index 000000000..22814b047 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-external-auth-one/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-external-auth-one", | ||
3 | "version": "0.0.1", | ||
4 | "description": "External auth one", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-external-auth-two/main.js b/server/tests/fixtures/peertube-plugin-test-external-auth-two/main.js new file mode 100644 index 000000000..126905ffc --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-external-auth-two/main.js | |||
@@ -0,0 +1,31 @@ | |||
1 | async function register ({ | ||
2 | registerExternalAuth, | ||
3 | peertubeHelpers | ||
4 | }) { | ||
5 | { | ||
6 | const result = registerExternalAuth({ | ||
7 | authName: 'external-auth-3', | ||
8 | authDisplayName: () => 'External Auth 3', | ||
9 | onAuthRequest: (req, res) => { | ||
10 | result.userAuthenticated({ | ||
11 | req, | ||
12 | res, | ||
13 | username: 'cid', | ||
14 | email: 'cid@example.com', | ||
15 | displayName: 'Cid Marquez' | ||
16 | }) | ||
17 | } | ||
18 | }) | ||
19 | } | ||
20 | } | ||
21 | |||
22 | async function unregister () { | ||
23 | return | ||
24 | } | ||
25 | |||
26 | module.exports = { | ||
27 | register, | ||
28 | unregister | ||
29 | } | ||
30 | |||
31 | // ########################################################################### | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-external-auth-two/package.json b/server/tests/fixtures/peertube-plugin-test-external-auth-two/package.json new file mode 100644 index 000000000..a5ca4d07a --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-external-auth-two/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-external-auth-two", | ||
3 | "version": "0.0.1", | ||
4 | "description": "External auth two", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-five/main.js b/server/tests/fixtures/peertube-plugin-test-five/main.js new file mode 100644 index 000000000..c1435b928 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-five/main.js | |||
@@ -0,0 +1,21 @@ | |||
1 | async function register ({ | ||
2 | getRouter | ||
3 | }) { | ||
4 | const router = getRouter() | ||
5 | router.get('/ping', (req, res) => res.json({ message: 'pong' })) | ||
6 | |||
7 | router.post('/form/post/mirror', (req, res) => { | ||
8 | res.json(req.body) | ||
9 | }) | ||
10 | } | ||
11 | |||
12 | async function unregister () { | ||
13 | return | ||
14 | } | ||
15 | |||
16 | module.exports = { | ||
17 | register, | ||
18 | unregister | ||
19 | } | ||
20 | |||
21 | // ########################################################################### | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-five/package.json b/server/tests/fixtures/peertube-plugin-test-five/package.json new file mode 100644 index 000000000..1f5d65d9d --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-five/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-five", | ||
3 | "version": "0.0.1", | ||
4 | "description": "Plugin test 5", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-four/main.js b/server/tests/fixtures/peertube-plugin-test-four/main.js new file mode 100644 index 000000000..067c3fe15 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-four/main.js | |||
@@ -0,0 +1,114 @@ | |||
1 | async function register ({ | ||
2 | peertubeHelpers, | ||
3 | registerHook, | ||
4 | getRouter | ||
5 | }) { | ||
6 | const logger = peertubeHelpers.logger | ||
7 | |||
8 | logger.info('Hello world from plugin four') | ||
9 | |||
10 | { | ||
11 | const username = 'root' | ||
12 | const results = await peertubeHelpers.database.query( | ||
13 | 'SELECT "email" from "user" WHERE "username" = $username', | ||
14 | { | ||
15 | type: 'SELECT', | ||
16 | bind: { username } | ||
17 | } | ||
18 | ) | ||
19 | |||
20 | logger.info('root email is ' + results[0]['email']) | ||
21 | } | ||
22 | |||
23 | { | ||
24 | registerHook({ | ||
25 | target: 'action:api.video.viewed', | ||
26 | handler: async ({ video }) => { | ||
27 | const videoFromDB = await peertubeHelpers.videos.loadByUrl(video.url) | ||
28 | logger.info('video from DB uuid is %s.', videoFromDB.uuid) | ||
29 | |||
30 | await peertubeHelpers.videos.removeVideo(video.id) | ||
31 | |||
32 | logger.info('Video deleted by plugin four.') | ||
33 | } | ||
34 | }) | ||
35 | } | ||
36 | |||
37 | { | ||
38 | const serverActor = await peertubeHelpers.server.getServerActor() | ||
39 | logger.info('server actor name is %s', serverActor.preferredUsername) | ||
40 | } | ||
41 | |||
42 | { | ||
43 | logger.info('server url is %s', peertubeHelpers.config.getWebserverUrl()) | ||
44 | } | ||
45 | |||
46 | { | ||
47 | const actions = { | ||
48 | blockServer, | ||
49 | unblockServer, | ||
50 | blockAccount, | ||
51 | unblockAccount, | ||
52 | blacklist, | ||
53 | unblacklist | ||
54 | } | ||
55 | |||
56 | const router = getRouter() | ||
57 | router.post('/commander', async (req, res) => { | ||
58 | try { | ||
59 | await actions[req.body.command](peertubeHelpers, req.body) | ||
60 | |||
61 | res.sendStatus(204) | ||
62 | } catch (err) { | ||
63 | logger.error('Error in commander.', { err }) | ||
64 | res.sendStatus(500) | ||
65 | } | ||
66 | }) | ||
67 | } | ||
68 | } | ||
69 | |||
70 | async function unregister () { | ||
71 | return | ||
72 | } | ||
73 | |||
74 | module.exports = { | ||
75 | register, | ||
76 | unregister | ||
77 | } | ||
78 | |||
79 | // ########################################################################### | ||
80 | |||
81 | async function blockServer (peertubeHelpers, body) { | ||
82 | const serverActor = await peertubeHelpers.server.getServerActor() | ||
83 | |||
84 | await peertubeHelpers.moderation.blockServer({ byAccountId: serverActor.Account.id, hostToBlock: body.hostToBlock }) | ||
85 | } | ||
86 | |||
87 | async function unblockServer (peertubeHelpers, body) { | ||
88 | const serverActor = await peertubeHelpers.server.getServerActor() | ||
89 | |||
90 | await peertubeHelpers.moderation.unblockServer({ byAccountId: serverActor.Account.id, hostToUnblock: body.hostToUnblock }) | ||
91 | } | ||
92 | |||
93 | async function blockAccount (peertubeHelpers, body) { | ||
94 | const serverActor = await peertubeHelpers.server.getServerActor() | ||
95 | |||
96 | await peertubeHelpers.moderation.blockAccount({ byAccountId: serverActor.Account.id, handleToBlock: body.handleToBlock }) | ||
97 | } | ||
98 | |||
99 | async function unblockAccount (peertubeHelpers, body) { | ||
100 | const serverActor = await peertubeHelpers.server.getServerActor() | ||
101 | |||
102 | await peertubeHelpers.moderation.unblockAccount({ byAccountId: serverActor.Account.id, handleToUnblock: body.handleToUnblock }) | ||
103 | } | ||
104 | |||
105 | async function blacklist (peertubeHelpers, body) { | ||
106 | await peertubeHelpers.moderation.blacklistVideo({ | ||
107 | videoIdOrUUID: body.videoUUID, | ||
108 | createOptions: body | ||
109 | }) | ||
110 | } | ||
111 | |||
112 | async function unblacklist (peertubeHelpers, body) { | ||
113 | await peertubeHelpers.moderation.unblacklistVideo({ videoIdOrUUID: body.videoUUID }) | ||
114 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-four/package.json b/server/tests/fixtures/peertube-plugin-test-four/package.json new file mode 100644 index 000000000..dda3c7f37 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-four/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-four", | ||
3 | "version": "0.0.1", | ||
4 | "description": "Plugin test 4", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/main.js b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/main.js new file mode 100644 index 000000000..f58faa847 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/main.js | |||
@@ -0,0 +1,69 @@ | |||
1 | async function register ({ | ||
2 | registerIdAndPassAuth, | ||
3 | peertubeHelpers, | ||
4 | settingsManager, | ||
5 | unregisterIdAndPassAuth | ||
6 | }) { | ||
7 | registerIdAndPassAuth({ | ||
8 | authName: 'spyro-auth', | ||
9 | |||
10 | onLogout: () => { | ||
11 | peertubeHelpers.logger.info('On logout for auth 1 - 1') | ||
12 | }, | ||
13 | |||
14 | getWeight: () => 15, | ||
15 | |||
16 | login (body) { | ||
17 | if (body.id === 'spyro' && body.password === 'spyro password') { | ||
18 | return Promise.resolve({ | ||
19 | username: 'spyro', | ||
20 | email: 'spyro@example.com', | ||
21 | role: 2, | ||
22 | displayName: 'Spyro the Dragon' | ||
23 | }) | ||
24 | } | ||
25 | |||
26 | return null | ||
27 | } | ||
28 | }) | ||
29 | |||
30 | registerIdAndPassAuth({ | ||
31 | authName: 'crash-auth', | ||
32 | |||
33 | onLogout: () => { | ||
34 | peertubeHelpers.logger.info('On logout for auth 1 - 2') | ||
35 | }, | ||
36 | |||
37 | getWeight: () => 50, | ||
38 | |||
39 | login (body) { | ||
40 | if (body.id === 'crash' && body.password === 'crash password') { | ||
41 | return Promise.resolve({ | ||
42 | username: 'crash', | ||
43 | email: 'crash@example.com', | ||
44 | role: 1, | ||
45 | displayName: 'Crash Bandicoot' | ||
46 | }) | ||
47 | } | ||
48 | |||
49 | return null | ||
50 | } | ||
51 | }) | ||
52 | |||
53 | settingsManager.onSettingsChange(settings => { | ||
54 | if (settings.disableSpyro) { | ||
55 | unregisterIdAndPassAuth('spyro-auth') | ||
56 | } | ||
57 | }) | ||
58 | } | ||
59 | |||
60 | async function unregister () { | ||
61 | return | ||
62 | } | ||
63 | |||
64 | module.exports = { | ||
65 | register, | ||
66 | unregister | ||
67 | } | ||
68 | |||
69 | // ########################################################################### | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/package.json b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/package.json new file mode 100644 index 000000000..f8ad18a90 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-one/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-id-pass-auth-one", | ||
3 | "version": "0.0.1", | ||
4 | "description": "Id and pass auth one", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/main.js b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/main.js new file mode 100644 index 000000000..caa6a7ccd --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/main.js | |||
@@ -0,0 +1,106 @@ | |||
1 | async function register ({ | ||
2 | registerIdAndPassAuth, | ||
3 | peertubeHelpers | ||
4 | }) { | ||
5 | registerIdAndPassAuth({ | ||
6 | authName: 'laguna-bad-auth', | ||
7 | |||
8 | onLogout: () => { | ||
9 | peertubeHelpers.logger.info('On logout for auth 3 - 1') | ||
10 | }, | ||
11 | |||
12 | getWeight: () => 5, | ||
13 | |||
14 | login (body) { | ||
15 | if (body.id === 'laguna' && body.password === 'laguna password') { | ||
16 | return Promise.resolve({ | ||
17 | username: 'laguna', | ||
18 | email: 'laguna@example.com', | ||
19 | displayName: 'Laguna Loire' | ||
20 | }) | ||
21 | } | ||
22 | |||
23 | return null | ||
24 | } | ||
25 | }) | ||
26 | |||
27 | registerIdAndPassAuth({ | ||
28 | authName: 'ward-auth', | ||
29 | |||
30 | getWeight: () => 5, | ||
31 | |||
32 | login (body) { | ||
33 | if (body.id === 'ward') { | ||
34 | return Promise.resolve({ | ||
35 | username: 'ward-42', | ||
36 | email: 'ward@example.com' | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | return null | ||
41 | } | ||
42 | }) | ||
43 | |||
44 | registerIdAndPassAuth({ | ||
45 | authName: 'kiros-auth', | ||
46 | |||
47 | getWeight: () => 5, | ||
48 | |||
49 | login (body) { | ||
50 | if (body.id === 'kiros') { | ||
51 | return Promise.resolve({ | ||
52 | username: 'kiros', | ||
53 | email: 'kiros@example.com', | ||
54 | displayName: 'a'.repeat(5000) | ||
55 | }) | ||
56 | } | ||
57 | |||
58 | return null | ||
59 | } | ||
60 | }) | ||
61 | |||
62 | registerIdAndPassAuth({ | ||
63 | authName: 'raine-auth', | ||
64 | |||
65 | getWeight: () => 5, | ||
66 | |||
67 | login (body) { | ||
68 | if (body.id === 'raine') { | ||
69 | return Promise.resolve({ | ||
70 | username: 'raine', | ||
71 | email: 'raine@example.com', | ||
72 | role: 42 | ||
73 | }) | ||
74 | } | ||
75 | |||
76 | return null | ||
77 | } | ||
78 | }) | ||
79 | |||
80 | registerIdAndPassAuth({ | ||
81 | authName: 'ellone-auth', | ||
82 | |||
83 | getWeight: () => 5, | ||
84 | |||
85 | login (body) { | ||
86 | if (body.id === 'ellone') { | ||
87 | return Promise.resolve({ | ||
88 | username: 'ellone' | ||
89 | }) | ||
90 | } | ||
91 | |||
92 | return null | ||
93 | } | ||
94 | }) | ||
95 | } | ||
96 | |||
97 | async function unregister () { | ||
98 | return | ||
99 | } | ||
100 | |||
101 | module.exports = { | ||
102 | register, | ||
103 | unregister | ||
104 | } | ||
105 | |||
106 | // ########################################################################### | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/package.json b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/package.json new file mode 100644 index 000000000..f9f107b1a --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-three/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-id-pass-auth-three", | ||
3 | "version": "0.0.1", | ||
4 | "description": "Id and pass auth three", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js new file mode 100644 index 000000000..ceab7b60d --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/main.js | |||
@@ -0,0 +1,54 @@ | |||
1 | async function register ({ | ||
2 | registerIdAndPassAuth, | ||
3 | peertubeHelpers | ||
4 | }) { | ||
5 | registerIdAndPassAuth({ | ||
6 | authName: 'laguna-auth', | ||
7 | |||
8 | onLogout: () => { | ||
9 | peertubeHelpers.logger.info('On logout for auth 2 - 1') | ||
10 | }, | ||
11 | |||
12 | getWeight: () => 30, | ||
13 | |||
14 | hookTokenValidity: (options) => { | ||
15 | if (options.type === 'refresh') { | ||
16 | return { valid: false } | ||
17 | } | ||
18 | |||
19 | if (options.type === 'access') { | ||
20 | const token = options.token | ||
21 | const now = new Date() | ||
22 | now.setTime(now.getTime() - 5000) | ||
23 | |||
24 | const createdAt = new Date(token.createdAt) | ||
25 | |||
26 | return { valid: createdAt.getTime() >= now.getTime() } | ||
27 | } | ||
28 | |||
29 | return { valid: true } | ||
30 | }, | ||
31 | |||
32 | login (body) { | ||
33 | if (body.id === 'laguna' && body.password === 'laguna password') { | ||
34 | return Promise.resolve({ | ||
35 | username: 'laguna', | ||
36 | email: 'laguna@example.com' | ||
37 | }) | ||
38 | } | ||
39 | |||
40 | return null | ||
41 | } | ||
42 | }) | ||
43 | } | ||
44 | |||
45 | async function unregister () { | ||
46 | return | ||
47 | } | ||
48 | |||
49 | module.exports = { | ||
50 | register, | ||
51 | unregister | ||
52 | } | ||
53 | |||
54 | // ########################################################################### | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/package.json b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/package.json new file mode 100644 index 000000000..5df15fac1 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-id-pass-auth-two/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-id-pass-auth-two", | ||
3 | "version": "0.0.1", | ||
4 | "description": "Id and pass auth two", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-six/main.js b/server/tests/fixtures/peertube-plugin-test-six/main.js new file mode 100644 index 000000000..bb9aaffa7 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-six/main.js | |||
@@ -0,0 +1,25 @@ | |||
1 | async function register ({ | ||
2 | storageManager, | ||
3 | peertubeHelpers | ||
4 | }) { | ||
5 | const { logger } = peertubeHelpers | ||
6 | |||
7 | { | ||
8 | await storageManager.storeData('superkey', { value: 'toto' }) | ||
9 | await storageManager.storeData('anotherkey', { value: 'toto2' }) | ||
10 | |||
11 | const result = await storageManager.getData('superkey') | ||
12 | logger.info('superkey stored value is %s', result.value) | ||
13 | } | ||
14 | } | ||
15 | |||
16 | async function unregister () { | ||
17 | return | ||
18 | } | ||
19 | |||
20 | module.exports = { | ||
21 | register, | ||
22 | unregister | ||
23 | } | ||
24 | |||
25 | // ########################################################################### | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-six/package.json b/server/tests/fixtures/peertube-plugin-test-six/package.json new file mode 100644 index 000000000..8c97826b0 --- /dev/null +++ b/server/tests/fixtures/peertube-plugin-test-six/package.json | |||
@@ -0,0 +1,20 @@ | |||
1 | { | ||
2 | "name": "peertube-plugin-test-six", | ||
3 | "version": "0.0.1", | ||
4 | "description": "Plugin test 6", | ||
5 | "engine": { | ||
6 | "peertube": ">=1.3.0" | ||
7 | }, | ||
8 | "keywords": [ | ||
9 | "peertube", | ||
10 | "plugin" | ||
11 | ], | ||
12 | "homepage": "https://github.com/Chocobozzz/PeerTube", | ||
13 | "author": "Chocobozzz", | ||
14 | "bugs": "https://github.com/Chocobozzz/PeerTube/issues", | ||
15 | "library": "./main.js", | ||
16 | "staticDirs": {}, | ||
17 | "css": [], | ||
18 | "clientScripts": [], | ||
19 | "translations": {} | ||
20 | } | ||
diff --git a/server/tests/fixtures/peertube-plugin-test-three/main.js b/server/tests/fixtures/peertube-plugin-test-three/main.js index 4945feb55..f2b89bcf0 100644 --- a/server/tests/fixtures/peertube-plugin-test-three/main.js +++ b/server/tests/fixtures/peertube-plugin-test-three/main.js | |||
@@ -5,7 +5,9 @@ async function register ({ | |||
5 | storageManager, | 5 | storageManager, |
6 | videoCategoryManager, | 6 | videoCategoryManager, |
7 | videoLicenceManager, | 7 | videoLicenceManager, |
8 | videoLanguageManager | 8 | videoLanguageManager, |
9 | videoPrivacyManager, | ||
10 | playlistPrivacyManager | ||
9 | }) { | 11 | }) { |
10 | videoLanguageManager.addLanguage('al_bhed', 'Al Bhed') | 12 | videoLanguageManager.addLanguage('al_bhed', 'Al Bhed') |
11 | videoLanguageManager.addLanguage('al_bhed2', 'Al Bhed 2') | 13 | videoLanguageManager.addLanguage('al_bhed2', 'Al Bhed 2') |
@@ -21,6 +23,9 @@ async function register ({ | |||
21 | videoLicenceManager.addLicence(43, 'High best licence') | 23 | videoLicenceManager.addLicence(43, 'High best licence') |
22 | videoLicenceManager.deleteLicence(1) // Attribution | 24 | videoLicenceManager.deleteLicence(1) // Attribution |
23 | videoLicenceManager.deleteLicence(7) // Public domain | 25 | videoLicenceManager.deleteLicence(7) // Public domain |
26 | |||
27 | videoPrivacyManager.deletePrivacy(2) | ||
28 | playlistPrivacyManager.deletePlaylistPrivacy(3) | ||
24 | } | 29 | } |
25 | 30 | ||
26 | async function unregister () { | 31 | async function unregister () { |
diff --git a/server/tests/fixtures/video_import_preview.jpg b/server/tests/fixtures/video_import_preview.jpg new file mode 100644 index 000000000..1f8d1d91d --- /dev/null +++ b/server/tests/fixtures/video_import_preview.jpg | |||
Binary files differ | |||
diff --git a/server/tests/fixtures/video_import_thumbnail.jpg b/server/tests/fixtures/video_import_thumbnail.jpg new file mode 100644 index 000000000..fcc50b75f --- /dev/null +++ b/server/tests/fixtures/video_import_thumbnail.jpg | |||
Binary files differ | |||
diff --git a/server/tests/helpers/comment-model.ts b/server/tests/helpers/comment-model.ts index ebfd779e1..4c51b7000 100644 --- a/server/tests/helpers/comment-model.ts +++ b/server/tests/helpers/comment-model.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
@@ -20,7 +20,7 @@ describe('Comment model', function () { | |||
20 | 20 | ||
21 | comment.text = '@florian @jean@localhost:9000 @flo @another@localhost:9000 @flo2@jean.com hello ' + | 21 | comment.text = '@florian @jean@localhost:9000 @flo @another@localhost:9000 @flo2@jean.com hello ' + |
22 | 'email@localhost:9000 coucou.com no? @chocobozzz @chocobozzz @end' | 22 | 'email@localhost:9000 coucou.com no? @chocobozzz @chocobozzz @end' |
23 | const result = comment.extractMentions().sort() | 23 | const result = comment.extractMentions().sort((a, b) => a.localeCompare(b)) |
24 | 24 | ||
25 | expect(result).to.deep.equal([ 'another', 'chocobozzz', 'end', 'flo', 'florian', 'jean' ]) | 25 | expect(result).to.deep.equal([ 'another', 'chocobozzz', 'end', 'flo', 'florian', 'jean' ]) |
26 | }) | 26 | }) |
diff --git a/server/tests/helpers/core-utils.ts b/server/tests/helpers/core-utils.ts index 31fc6dd7c..c028b316d 100644 --- a/server/tests/helpers/core-utils.ts +++ b/server/tests/helpers/core-utils.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
diff --git a/server/tests/helpers/request.ts b/server/tests/helpers/request.ts index a754bc6e2..f8b2d599b 100644 --- a/server/tests/helpers/request.ts +++ b/server/tests/helpers/request.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' | 4 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' |
diff --git a/server/tests/index.ts b/server/tests/index.ts index 8bddcfc7c..3fbd0ebbd 100644 --- a/server/tests/index.ts +++ b/server/tests/index.ts | |||
@@ -1,6 +1,8 @@ | |||
1 | // Order of the tests we want to execute | 1 | // Order of the tests we want to execute |
2 | import './client' | 2 | import './client' |
3 | import './misc-endpoints' | ||
3 | import './feeds/' | 4 | import './feeds/' |
4 | import './cli/' | 5 | import './cli/' |
5 | import './api/' | 6 | import './api/' |
6 | import './plugins/' | 7 | import './plugins/' |
8 | import './helpers/' | ||
diff --git a/server/tests/misc-endpoints.ts b/server/tests/misc-endpoints.ts index ab2dd3a0f..32b035c9e 100644 --- a/server/tests/misc-endpoints.ts +++ b/server/tests/misc-endpoints.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
diff --git a/server/tests/plugins/action-hooks.ts b/server/tests/plugins/action-hooks.ts index 510ec3151..ca57a4b51 100644 --- a/server/tests/plugins/action-hooks.ts +++ b/server/tests/plugins/action-hooks.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { | 4 | import { |
6 | cleanupTests, | 5 | cleanupTests, |
@@ -17,18 +16,18 @@ import { | |||
17 | createUser, | 16 | createUser, |
18 | deleteVideoComment, | 17 | deleteVideoComment, |
19 | getPluginTestPath, | 18 | getPluginTestPath, |
20 | installPlugin, login, | 19 | installPlugin, |
21 | registerUser, removeUser, | 20 | registerUser, |
21 | removeUser, | ||
22 | setAccessTokensToServers, | 22 | setAccessTokensToServers, |
23 | unblockUser, updateUser, | 23 | unblockUser, |
24 | updateUser, | ||
24 | updateVideo, | 25 | updateVideo, |
25 | uploadVideo, | 26 | uploadVideo, |
26 | viewVideo, | 27 | userLogin, |
27 | userLogin | 28 | viewVideo |
28 | } from '../../../shared/extra-utils' | 29 | } from '../../../shared/extra-utils' |
29 | 30 | ||
30 | const expect = chai.expect | ||
31 | |||
32 | describe('Test plugin action hooks', function () { | 31 | describe('Test plugin action hooks', function () { |
33 | let servers: ServerInfo[] | 32 | let servers: ServerInfo[] |
34 | let videoUUID: string | 33 | let videoUUID: string |
diff --git a/server/tests/plugins/external-auth.ts b/server/tests/plugins/external-auth.ts new file mode 100644 index 000000000..312561538 --- /dev/null +++ b/server/tests/plugins/external-auth.ts | |||
@@ -0,0 +1,331 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { expect } from 'chai' | ||
5 | import { ServerConfig, User, UserRole } from '@shared/models' | ||
6 | import { | ||
7 | decodeQueryString, | ||
8 | getConfig, | ||
9 | getExternalAuth, | ||
10 | getMyUserInformation, | ||
11 | getPluginTestPath, | ||
12 | installPlugin, | ||
13 | loginUsingExternalToken, | ||
14 | logout, | ||
15 | refreshToken, | ||
16 | setAccessTokensToServers, | ||
17 | uninstallPlugin, | ||
18 | updateMyUser, | ||
19 | wait, | ||
20 | userLogin, | ||
21 | updatePluginSettings | ||
22 | } from '../../../shared/extra-utils' | ||
23 | import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' | ||
24 | |||
25 | async function loginExternal (options: { | ||
26 | server: ServerInfo | ||
27 | npmName: string | ||
28 | authName: string | ||
29 | username: string | ||
30 | query?: any | ||
31 | statusCodeExpected?: number | ||
32 | }) { | ||
33 | const res = await getExternalAuth({ | ||
34 | url: options.server.url, | ||
35 | npmName: options.npmName, | ||
36 | npmVersion: '0.0.1', | ||
37 | authName: options.authName, | ||
38 | query: options.query, | ||
39 | statusCodeExpected: options.statusCodeExpected || 302 | ||
40 | }) | ||
41 | |||
42 | if (res.status !== 302) return | ||
43 | |||
44 | const location = res.header.location | ||
45 | const { externalAuthToken } = decodeQueryString(location) | ||
46 | |||
47 | const resLogin = await loginUsingExternalToken( | ||
48 | options.server, | ||
49 | options.username, | ||
50 | externalAuthToken as string | ||
51 | ) | ||
52 | |||
53 | return resLogin.body | ||
54 | } | ||
55 | |||
56 | describe('Test external auth plugins', function () { | ||
57 | let server: ServerInfo | ||
58 | |||
59 | let cyanAccessToken: string | ||
60 | let cyanRefreshToken: string | ||
61 | |||
62 | let kefkaAccessToken: string | ||
63 | let kefkaRefreshToken: string | ||
64 | |||
65 | let externalAuthToken: string | ||
66 | |||
67 | before(async function () { | ||
68 | this.timeout(30000) | ||
69 | |||
70 | server = await flushAndRunServer(1) | ||
71 | await setAccessTokensToServers([ server ]) | ||
72 | |||
73 | for (const suffix of [ 'one', 'two' ]) { | ||
74 | await installPlugin({ | ||
75 | url: server.url, | ||
76 | accessToken: server.accessToken, | ||
77 | path: getPluginTestPath('-external-auth-' + suffix) | ||
78 | }) | ||
79 | } | ||
80 | }) | ||
81 | |||
82 | it('Should display the correct configuration', async function () { | ||
83 | const res = await getConfig(server.url) | ||
84 | |||
85 | const config: ServerConfig = res.body | ||
86 | |||
87 | const auths = config.plugin.registeredExternalAuths | ||
88 | expect(auths).to.have.lengthOf(3) | ||
89 | |||
90 | const auth2 = auths.find((a) => a.authName === 'external-auth-2') | ||
91 | expect(auth2).to.exist | ||
92 | expect(auth2.authDisplayName).to.equal('External Auth 2') | ||
93 | expect(auth2.npmName).to.equal('peertube-plugin-test-external-auth-one') | ||
94 | }) | ||
95 | |||
96 | it('Should redirect for a Cyan login', async function () { | ||
97 | const res = await getExternalAuth({ | ||
98 | url: server.url, | ||
99 | npmName: 'test-external-auth-one', | ||
100 | npmVersion: '0.0.1', | ||
101 | authName: 'external-auth-1', | ||
102 | query: { | ||
103 | username: 'cyan' | ||
104 | }, | ||
105 | statusCodeExpected: 302 | ||
106 | }) | ||
107 | |||
108 | const location = res.header.location | ||
109 | expect(location.startsWith('/login?')).to.be.true | ||
110 | |||
111 | const searchParams = decodeQueryString(location) | ||
112 | |||
113 | expect(searchParams.externalAuthToken).to.exist | ||
114 | expect(searchParams.username).to.equal('cyan') | ||
115 | |||
116 | externalAuthToken = searchParams.externalAuthToken as string | ||
117 | }) | ||
118 | |||
119 | it('Should reject auto external login with a missing or invalid token', async function () { | ||
120 | await loginUsingExternalToken(server, 'cyan', '', 400) | ||
121 | await loginUsingExternalToken(server, 'cyan', 'blabla', 400) | ||
122 | }) | ||
123 | |||
124 | it('Should reject auto external login with a missing or invalid username', async function () { | ||
125 | await loginUsingExternalToken(server, '', externalAuthToken, 400) | ||
126 | await loginUsingExternalToken(server, '', externalAuthToken, 400) | ||
127 | }) | ||
128 | |||
129 | it('Should reject auto external login with an expired token', async function () { | ||
130 | this.timeout(15000) | ||
131 | |||
132 | await wait(5000) | ||
133 | |||
134 | await loginUsingExternalToken(server, 'cyan', externalAuthToken, 400) | ||
135 | |||
136 | await waitUntilLog(server, 'expired external auth token') | ||
137 | }) | ||
138 | |||
139 | it('Should auto login Cyan, create the user and use the token', async function () { | ||
140 | { | ||
141 | const res = await loginExternal({ | ||
142 | server, | ||
143 | npmName: 'test-external-auth-one', | ||
144 | authName: 'external-auth-1', | ||
145 | query: { | ||
146 | username: 'cyan' | ||
147 | }, | ||
148 | username: 'cyan' | ||
149 | }) | ||
150 | |||
151 | cyanAccessToken = res.access_token | ||
152 | cyanRefreshToken = res.refresh_token | ||
153 | } | ||
154 | |||
155 | { | ||
156 | const res = await getMyUserInformation(server.url, cyanAccessToken) | ||
157 | |||
158 | const body: User = res.body | ||
159 | expect(body.username).to.equal('cyan') | ||
160 | expect(body.account.displayName).to.equal('cyan') | ||
161 | expect(body.email).to.equal('cyan@example.com') | ||
162 | expect(body.role).to.equal(UserRole.USER) | ||
163 | } | ||
164 | }) | ||
165 | |||
166 | it('Should auto login Kefka, create the user and use the token', async function () { | ||
167 | { | ||
168 | const res = await loginExternal({ | ||
169 | server, | ||
170 | npmName: 'test-external-auth-one', | ||
171 | authName: 'external-auth-2', | ||
172 | username: 'kefka' | ||
173 | }) | ||
174 | |||
175 | kefkaAccessToken = res.access_token | ||
176 | kefkaRefreshToken = res.refresh_token | ||
177 | } | ||
178 | |||
179 | { | ||
180 | const res = await getMyUserInformation(server.url, kefkaAccessToken) | ||
181 | |||
182 | const body: User = res.body | ||
183 | expect(body.username).to.equal('kefka') | ||
184 | expect(body.account.displayName).to.equal('Kefka Palazzo') | ||
185 | expect(body.email).to.equal('kefka@example.com') | ||
186 | expect(body.role).to.equal(UserRole.ADMINISTRATOR) | ||
187 | } | ||
188 | }) | ||
189 | |||
190 | it('Should refresh Cyan token, but not Kefka token', async function () { | ||
191 | { | ||
192 | const resRefresh = await refreshToken(server, cyanRefreshToken) | ||
193 | cyanAccessToken = resRefresh.body.access_token | ||
194 | cyanRefreshToken = resRefresh.body.refresh_token | ||
195 | |||
196 | const res = await getMyUserInformation(server.url, cyanAccessToken) | ||
197 | const user: User = res.body | ||
198 | expect(user.username).to.equal('cyan') | ||
199 | } | ||
200 | |||
201 | { | ||
202 | await refreshToken(server, kefkaRefreshToken, 400) | ||
203 | } | ||
204 | }) | ||
205 | |||
206 | it('Should update Cyan profile', async function () { | ||
207 | await updateMyUser({ | ||
208 | url: server.url, | ||
209 | accessToken: cyanAccessToken, | ||
210 | displayName: 'Cyan Garamonde', | ||
211 | description: 'Retainer to the king of Doma' | ||
212 | }) | ||
213 | |||
214 | const res = await getMyUserInformation(server.url, cyanAccessToken) | ||
215 | |||
216 | const body: User = res.body | ||
217 | expect(body.account.displayName).to.equal('Cyan Garamonde') | ||
218 | expect(body.account.description).to.equal('Retainer to the king of Doma') | ||
219 | }) | ||
220 | |||
221 | it('Should logout Cyan', async function () { | ||
222 | await logout(server.url, cyanAccessToken) | ||
223 | }) | ||
224 | |||
225 | it('Should have logged out Cyan', async function () { | ||
226 | await waitUntilLog(server, 'On logout cyan') | ||
227 | |||
228 | await getMyUserInformation(server.url, cyanAccessToken, 401) | ||
229 | }) | ||
230 | |||
231 | it('Should login Cyan and keep the old existing profile', async function () { | ||
232 | { | ||
233 | const res = await loginExternal({ | ||
234 | server, | ||
235 | npmName: 'test-external-auth-one', | ||
236 | authName: 'external-auth-1', | ||
237 | query: { | ||
238 | username: 'cyan' | ||
239 | }, | ||
240 | username: 'cyan' | ||
241 | }) | ||
242 | |||
243 | cyanAccessToken = res.access_token | ||
244 | } | ||
245 | |||
246 | const res = await getMyUserInformation(server.url, cyanAccessToken) | ||
247 | |||
248 | const body: User = res.body | ||
249 | expect(body.username).to.equal('cyan') | ||
250 | expect(body.account.displayName).to.equal('Cyan Garamonde') | ||
251 | expect(body.account.description).to.equal('Retainer to the king of Doma') | ||
252 | expect(body.role).to.equal(UserRole.USER) | ||
253 | }) | ||
254 | |||
255 | it('Should reject token of Kefka by the plugin hook', async function () { | ||
256 | this.timeout(10000) | ||
257 | |||
258 | await wait(5000) | ||
259 | |||
260 | await getMyUserInformation(server.url, kefkaAccessToken, 401) | ||
261 | }) | ||
262 | |||
263 | it('Should unregister external-auth-2 and do not login existing Kefka', async function () { | ||
264 | await updatePluginSettings({ | ||
265 | url: server.url, | ||
266 | accessToken: server.accessToken, | ||
267 | npmName: 'peertube-plugin-test-external-auth-one', | ||
268 | settings: { disableKefka: true } | ||
269 | }) | ||
270 | |||
271 | await userLogin(server, { username: 'kefka', password: 'fake' }, 400) | ||
272 | |||
273 | await loginExternal({ | ||
274 | server, | ||
275 | npmName: 'test-external-auth-one', | ||
276 | authName: 'external-auth-2', | ||
277 | query: { | ||
278 | username: 'kefka' | ||
279 | }, | ||
280 | username: 'kefka', | ||
281 | statusCodeExpected: 404 | ||
282 | }) | ||
283 | }) | ||
284 | |||
285 | it('Should have disabled this auth', async function () { | ||
286 | const res = await getConfig(server.url) | ||
287 | |||
288 | const config: ServerConfig = res.body | ||
289 | |||
290 | const auths = config.plugin.registeredExternalAuths | ||
291 | expect(auths).to.have.lengthOf(2) | ||
292 | |||
293 | const auth1 = auths.find(a => a.authName === 'external-auth-2') | ||
294 | expect(auth1).to.not.exist | ||
295 | }) | ||
296 | |||
297 | it('Should uninstall the plugin one and do not login Cyan', async function () { | ||
298 | await uninstallPlugin({ | ||
299 | url: server.url, | ||
300 | accessToken: server.accessToken, | ||
301 | npmName: 'peertube-plugin-test-external-auth-one' | ||
302 | }) | ||
303 | |||
304 | await loginExternal({ | ||
305 | server, | ||
306 | npmName: 'test-external-auth-one', | ||
307 | authName: 'external-auth-1', | ||
308 | query: { | ||
309 | username: 'cyan' | ||
310 | }, | ||
311 | username: 'cyan', | ||
312 | statusCodeExpected: 404 | ||
313 | }) | ||
314 | }) | ||
315 | |||
316 | it('Should display the correct configuration', async function () { | ||
317 | const res = await getConfig(server.url) | ||
318 | |||
319 | const config: ServerConfig = res.body | ||
320 | |||
321 | const auths = config.plugin.registeredExternalAuths | ||
322 | expect(auths).to.have.lengthOf(1) | ||
323 | |||
324 | const auth2 = auths.find((a) => a.authName === 'external-auth-2') | ||
325 | expect(auth2).to.not.exist | ||
326 | }) | ||
327 | |||
328 | after(async function () { | ||
329 | await cleanupTests([ server ]) | ||
330 | }) | ||
331 | }) | ||
diff --git a/server/tests/plugins/filter-hooks.ts b/server/tests/plugins/filter-hooks.ts index 6a5ea4641..6c1fd40ba 100644 --- a/server/tests/plugins/filter-hooks.ts +++ b/server/tests/plugins/filter-hooks.ts | |||
@@ -1,34 +1,27 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { cleanupTests, flushAndRunMultipleServers, ServerInfo } from '../../../shared/extra-utils/server/servers' |
6 | cleanupTests, | ||
7 | flushAndRunMultipleServers, | ||
8 | flushAndRunServer, killallServers, reRunServer, | ||
9 | ServerInfo, | ||
10 | waitUntilLog | ||
11 | } from '../../../shared/extra-utils/server/servers' | ||
12 | import { | 6 | import { |
13 | addVideoCommentReply, | 7 | addVideoCommentReply, |
14 | addVideoCommentThread, | 8 | addVideoCommentThread, |
15 | deleteVideoComment, | 9 | doubleFollow, |
10 | getConfig, | ||
16 | getPluginTestPath, | 11 | getPluginTestPath, |
17 | getVideosList, | ||
18 | installPlugin, | ||
19 | removeVideo, | ||
20 | setAccessTokensToServers, | ||
21 | updateVideo, | ||
22 | uploadVideo, | ||
23 | viewVideo, | ||
24 | getVideosListPagination, | ||
25 | getVideo, | 12 | getVideo, |
26 | getVideoCommentThreads, | 13 | getVideoCommentThreads, |
14 | getVideosList, | ||
15 | getVideosListPagination, | ||
27 | getVideoThreadComments, | 16 | getVideoThreadComments, |
28 | getVideoWithToken, | 17 | getVideoWithToken, |
18 | installPlugin, | ||
19 | registerUser, | ||
20 | setAccessTokensToServers, | ||
29 | setDefaultVideoChannel, | 21 | setDefaultVideoChannel, |
30 | waitJobs, | 22 | updateVideo, |
31 | doubleFollow, getConfig, registerUser | 23 | uploadVideo, |
24 | waitJobs | ||
32 | } from '../../../shared/extra-utils' | 25 | } from '../../../shared/extra-utils' |
33 | import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' | 26 | import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' |
34 | import { VideoDetails } from '../../../shared/models/videos' | 27 | import { VideoDetails } from '../../../shared/models/videos' |
@@ -140,7 +133,7 @@ describe('Test plugin filter hooks', function () { | |||
140 | } | 133 | } |
141 | 134 | ||
142 | it('Should blacklist on upload', async function () { | 135 | it('Should blacklist on upload', async function () { |
143 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video please blacklist me' }) | 136 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video please blacklist me' }) |
144 | await checkIsBlacklisted(res, true) | 137 | await checkIsBlacklisted(res, true) |
145 | }) | 138 | }) |
146 | 139 | ||
@@ -157,18 +150,18 @@ describe('Test plugin filter hooks', function () { | |||
157 | }) | 150 | }) |
158 | 151 | ||
159 | it('Should blacklist on update', async function () { | 152 | it('Should blacklist on update', async function () { |
160 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video' }) | 153 | const res = await uploadVideo(servers[0].url, servers[0].accessToken, { name: 'video' }) |
161 | const videoId = res.body.video.uuid | 154 | const videoId = res.body.video.uuid |
162 | await checkIsBlacklisted(res, false) | 155 | await checkIsBlacklisted(res, false) |
163 | 156 | ||
164 | await updateVideo(servers[ 0 ].url, servers[ 0 ].accessToken, videoId, { name: 'please blacklist me' }) | 157 | await updateVideo(servers[0].url, servers[0].accessToken, videoId, { name: 'please blacklist me' }) |
165 | await checkIsBlacklisted(res, true) | 158 | await checkIsBlacklisted(res, true) |
166 | }) | 159 | }) |
167 | 160 | ||
168 | it('Should blacklist on remote upload', async function () { | 161 | it('Should blacklist on remote upload', async function () { |
169 | this.timeout(45000) | 162 | this.timeout(45000) |
170 | 163 | ||
171 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'remote please blacklist me' }) | 164 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'remote please blacklist me' }) |
172 | await waitJobs(servers) | 165 | await waitJobs(servers) |
173 | 166 | ||
174 | await checkIsBlacklisted(res, true) | 167 | await checkIsBlacklisted(res, true) |
@@ -177,7 +170,7 @@ describe('Test plugin filter hooks', function () { | |||
177 | it('Should blacklist on remote update', async function () { | 170 | it('Should blacklist on remote update', async function () { |
178 | this.timeout(45000) | 171 | this.timeout(45000) |
179 | 172 | ||
180 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video' }) | 173 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video' }) |
181 | await waitJobs(servers) | 174 | await waitJobs(servers) |
182 | 175 | ||
183 | const videoId = res.body.video.uuid | 176 | const videoId = res.body.video.uuid |
diff --git a/server/tests/plugins/id-and-pass-auth.ts b/server/tests/plugins/id-and-pass-auth.ts new file mode 100644 index 000000000..cbba638c2 --- /dev/null +++ b/server/tests/plugins/id-and-pass-auth.ts | |||
@@ -0,0 +1,245 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' | ||
5 | import { | ||
6 | getMyUserInformation, | ||
7 | getPluginTestPath, | ||
8 | installPlugin, | ||
9 | logout, | ||
10 | setAccessTokensToServers, | ||
11 | uninstallPlugin, | ||
12 | updateMyUser, | ||
13 | userLogin, | ||
14 | wait, | ||
15 | login, refreshToken, getConfig, updatePluginSettings, getUsersList | ||
16 | } from '../../../shared/extra-utils' | ||
17 | import { User, UserRole, ServerConfig } from '@shared/models' | ||
18 | import { expect } from 'chai' | ||
19 | |||
20 | describe('Test id and pass auth plugins', function () { | ||
21 | let server: ServerInfo | ||
22 | |||
23 | let crashAccessToken: string | ||
24 | let crashRefreshToken: string | ||
25 | |||
26 | let lagunaAccessToken: string | ||
27 | let lagunaRefreshToken: string | ||
28 | |||
29 | before(async function () { | ||
30 | this.timeout(30000) | ||
31 | |||
32 | server = await flushAndRunServer(1) | ||
33 | await setAccessTokensToServers([ server ]) | ||
34 | |||
35 | for (const suffix of [ 'one', 'two', 'three' ]) { | ||
36 | await installPlugin({ | ||
37 | url: server.url, | ||
38 | accessToken: server.accessToken, | ||
39 | path: getPluginTestPath('-id-pass-auth-' + suffix) | ||
40 | }) | ||
41 | } | ||
42 | }) | ||
43 | |||
44 | it('Should display the correct configuration', async function () { | ||
45 | const res = await getConfig(server.url) | ||
46 | |||
47 | const config: ServerConfig = res.body | ||
48 | |||
49 | const auths = config.plugin.registeredIdAndPassAuths | ||
50 | expect(auths).to.have.lengthOf(8) | ||
51 | |||
52 | const crashAuth = auths.find(a => a.authName === 'crash-auth') | ||
53 | expect(crashAuth).to.exist | ||
54 | expect(crashAuth.npmName).to.equal('peertube-plugin-test-id-pass-auth-one') | ||
55 | expect(crashAuth.weight).to.equal(50) | ||
56 | }) | ||
57 | |||
58 | it('Should not login', async function () { | ||
59 | await userLogin(server, { username: 'toto', password: 'password' }, 400) | ||
60 | }) | ||
61 | |||
62 | it('Should login Spyro, create the user and use the token', async function () { | ||
63 | const accessToken = await userLogin(server, { username: 'spyro', password: 'spyro password' }) | ||
64 | |||
65 | const res = await getMyUserInformation(server.url, accessToken) | ||
66 | |||
67 | const body: User = res.body | ||
68 | expect(body.username).to.equal('spyro') | ||
69 | expect(body.account.displayName).to.equal('Spyro the Dragon') | ||
70 | expect(body.role).to.equal(UserRole.USER) | ||
71 | }) | ||
72 | |||
73 | it('Should login Crash, create the user and use the token', async function () { | ||
74 | { | ||
75 | const res = await login(server.url, server.client, { username: 'crash', password: 'crash password' }) | ||
76 | crashAccessToken = res.body.access_token | ||
77 | crashRefreshToken = res.body.refresh_token | ||
78 | } | ||
79 | |||
80 | { | ||
81 | const res = await getMyUserInformation(server.url, crashAccessToken) | ||
82 | |||
83 | const body: User = res.body | ||
84 | expect(body.username).to.equal('crash') | ||
85 | expect(body.account.displayName).to.equal('Crash Bandicoot') | ||
86 | expect(body.role).to.equal(UserRole.MODERATOR) | ||
87 | } | ||
88 | }) | ||
89 | |||
90 | it('Should login the first Laguna, create the user and use the token', async function () { | ||
91 | { | ||
92 | const res = await login(server.url, server.client, { username: 'laguna', password: 'laguna password' }) | ||
93 | lagunaAccessToken = res.body.access_token | ||
94 | lagunaRefreshToken = res.body.refresh_token | ||
95 | } | ||
96 | |||
97 | { | ||
98 | const res = await getMyUserInformation(server.url, lagunaAccessToken) | ||
99 | |||
100 | const body: User = res.body | ||
101 | expect(body.username).to.equal('laguna') | ||
102 | expect(body.account.displayName).to.equal('laguna') | ||
103 | expect(body.role).to.equal(UserRole.USER) | ||
104 | } | ||
105 | }) | ||
106 | |||
107 | it('Should refresh crash token, but not laguna token', async function () { | ||
108 | { | ||
109 | const resRefresh = await refreshToken(server, crashRefreshToken) | ||
110 | crashAccessToken = resRefresh.body.access_token | ||
111 | crashRefreshToken = resRefresh.body.refresh_token | ||
112 | |||
113 | const res = await getMyUserInformation(server.url, crashAccessToken) | ||
114 | const user: User = res.body | ||
115 | expect(user.username).to.equal('crash') | ||
116 | } | ||
117 | |||
118 | { | ||
119 | await refreshToken(server, lagunaRefreshToken, 400) | ||
120 | } | ||
121 | }) | ||
122 | |||
123 | it('Should update Crash profile', async function () { | ||
124 | await updateMyUser({ | ||
125 | url: server.url, | ||
126 | accessToken: crashAccessToken, | ||
127 | displayName: 'Beautiful Crash', | ||
128 | description: 'Mutant eastern barred bandicoot' | ||
129 | }) | ||
130 | |||
131 | const res = await getMyUserInformation(server.url, crashAccessToken) | ||
132 | |||
133 | const body: User = res.body | ||
134 | expect(body.account.displayName).to.equal('Beautiful Crash') | ||
135 | expect(body.account.description).to.equal('Mutant eastern barred bandicoot') | ||
136 | }) | ||
137 | |||
138 | it('Should logout Crash', async function () { | ||
139 | await logout(server.url, crashAccessToken) | ||
140 | }) | ||
141 | |||
142 | it('Should have logged out Crash', async function () { | ||
143 | await waitUntilLog(server, 'On logout for auth 1 - 2') | ||
144 | |||
145 | await getMyUserInformation(server.url, crashAccessToken, 401) | ||
146 | }) | ||
147 | |||
148 | it('Should login Crash and keep the old existing profile', async function () { | ||
149 | crashAccessToken = await userLogin(server, { username: 'crash', password: 'crash password' }) | ||
150 | |||
151 | const res = await getMyUserInformation(server.url, crashAccessToken) | ||
152 | |||
153 | const body: User = res.body | ||
154 | expect(body.username).to.equal('crash') | ||
155 | expect(body.account.displayName).to.equal('Beautiful Crash') | ||
156 | expect(body.account.description).to.equal('Mutant eastern barred bandicoot') | ||
157 | expect(body.role).to.equal(UserRole.MODERATOR) | ||
158 | }) | ||
159 | |||
160 | it('Should reject token of laguna by the plugin hook', async function () { | ||
161 | this.timeout(10000) | ||
162 | |||
163 | await wait(5000) | ||
164 | |||
165 | await getMyUserInformation(server.url, lagunaAccessToken, 401) | ||
166 | }) | ||
167 | |||
168 | it('Should reject an invalid username, email, role or display name', async function () { | ||
169 | await userLogin(server, { username: 'ward', password: 'ward password' }, 400) | ||
170 | await waitUntilLog(server, 'valid username') | ||
171 | |||
172 | await userLogin(server, { username: 'kiros', password: 'kiros password' }, 400) | ||
173 | await waitUntilLog(server, 'valid display name') | ||
174 | |||
175 | await userLogin(server, { username: 'raine', password: 'raine password' }, 400) | ||
176 | await waitUntilLog(server, 'valid role') | ||
177 | |||
178 | await userLogin(server, { username: 'ellone', password: 'elonne password' }, 400) | ||
179 | await waitUntilLog(server, 'valid email') | ||
180 | }) | ||
181 | |||
182 | it('Should unregister spyro-auth and do not login existing Spyro', async function () { | ||
183 | await updatePluginSettings({ | ||
184 | url: server.url, | ||
185 | accessToken: server.accessToken, | ||
186 | npmName: 'peertube-plugin-test-id-pass-auth-one', | ||
187 | settings: { disableSpyro: true } | ||
188 | }) | ||
189 | |||
190 | await userLogin(server, { username: 'spyro', password: 'spyro password' }, 400) | ||
191 | await userLogin(server, { username: 'spyro', password: 'fake' }, 400) | ||
192 | }) | ||
193 | |||
194 | it('Should have disabled this auth', async function () { | ||
195 | const res = await getConfig(server.url) | ||
196 | |||
197 | const config: ServerConfig = res.body | ||
198 | |||
199 | const auths = config.plugin.registeredIdAndPassAuths | ||
200 | expect(auths).to.have.lengthOf(7) | ||
201 | |||
202 | const spyroAuth = auths.find(a => a.authName === 'spyro-auth') | ||
203 | expect(spyroAuth).to.not.exist | ||
204 | }) | ||
205 | |||
206 | it('Should uninstall the plugin one and do not login existing Crash', async function () { | ||
207 | await uninstallPlugin({ | ||
208 | url: server.url, | ||
209 | accessToken: server.accessToken, | ||
210 | npmName: 'peertube-plugin-test-id-pass-auth-one' | ||
211 | }) | ||
212 | |||
213 | await userLogin(server, { username: 'crash', password: 'crash password' }, 400) | ||
214 | }) | ||
215 | |||
216 | it('Should display the correct configuration', async function () { | ||
217 | const res = await getConfig(server.url) | ||
218 | |||
219 | const config: ServerConfig = res.body | ||
220 | |||
221 | const auths = config.plugin.registeredIdAndPassAuths | ||
222 | expect(auths).to.have.lengthOf(6) | ||
223 | |||
224 | const crashAuth = auths.find(a => a.authName === 'crash-auth') | ||
225 | expect(crashAuth).to.not.exist | ||
226 | }) | ||
227 | |||
228 | it('Should display plugin auth information in users list', async function () { | ||
229 | const res = await getUsersList(server.url, server.accessToken) | ||
230 | |||
231 | const users: User[] = res.body.data | ||
232 | |||
233 | const root = users.find(u => u.username === 'root') | ||
234 | const crash = users.find(u => u.username === 'crash') | ||
235 | const laguna = users.find(u => u.username === 'laguna') | ||
236 | |||
237 | expect(root.pluginAuth).to.be.null | ||
238 | expect(crash.pluginAuth).to.equal('peertube-plugin-test-id-pass-auth-one') | ||
239 | expect(laguna.pluginAuth).to.equal('peertube-plugin-test-id-pass-auth-two') | ||
240 | }) | ||
241 | |||
242 | after(async function () { | ||
243 | await cleanupTests([ server ]) | ||
244 | }) | ||
245 | }) | ||
diff --git a/server/tests/plugins/index.ts b/server/tests/plugins/index.ts index f41708055..39c4c958a 100644 --- a/server/tests/plugins/index.ts +++ b/server/tests/plugins/index.ts | |||
@@ -1,4 +1,9 @@ | |||
1 | import './action-hooks' | 1 | import './action-hooks' |
2 | import './id-and-pass-auth' | ||
3 | import './external-auth' | ||
2 | import './filter-hooks' | 4 | import './filter-hooks' |
3 | import './translations' | 5 | import './translations' |
4 | import './video-constants' | 6 | import './video-constants' |
7 | import './plugin-helpers' | ||
8 | import './plugin-router' | ||
9 | import './plugin-storage' | ||
diff --git a/server/tests/plugins/plugin-helpers.ts b/server/tests/plugins/plugin-helpers.ts new file mode 100644 index 000000000..0915603d0 --- /dev/null +++ b/server/tests/plugins/plugin-helpers.ts | |||
@@ -0,0 +1,210 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { | ||
5 | checkVideoFilesWereRemoved, | ||
6 | doubleFollow, | ||
7 | getPluginTestPath, | ||
8 | getVideo, | ||
9 | installPlugin, | ||
10 | makePostBodyRequest, | ||
11 | setAccessTokensToServers, | ||
12 | uploadVideoAndGetId, | ||
13 | viewVideo, | ||
14 | getVideosList, | ||
15 | waitJobs | ||
16 | } from '../../../shared/extra-utils' | ||
17 | import { cleanupTests, flushAndRunMultipleServers, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' | ||
18 | import { expect } from 'chai' | ||
19 | |||
20 | function postCommand (server: ServerInfo, command: string, bodyArg?: object) { | ||
21 | const body = { command } | ||
22 | if (bodyArg) Object.assign(body, bodyArg) | ||
23 | |||
24 | return makePostBodyRequest({ | ||
25 | url: server.url, | ||
26 | path: '/plugins/test-four/router/commander', | ||
27 | fields: body, | ||
28 | statusCodeExpected: 204 | ||
29 | }) | ||
30 | } | ||
31 | |||
32 | describe('Test plugin helpers', function () { | ||
33 | let servers: ServerInfo[] | ||
34 | |||
35 | before(async function () { | ||
36 | this.timeout(60000) | ||
37 | |||
38 | servers = await flushAndRunMultipleServers(2) | ||
39 | await setAccessTokensToServers(servers) | ||
40 | |||
41 | await doubleFollow(servers[0], servers[1]) | ||
42 | |||
43 | await installPlugin({ | ||
44 | url: servers[0].url, | ||
45 | accessToken: servers[0].accessToken, | ||
46 | path: getPluginTestPath('-four') | ||
47 | }) | ||
48 | }) | ||
49 | |||
50 | describe('Logger', function () { | ||
51 | |||
52 | it('Should have logged things', async function () { | ||
53 | await waitUntilLog(servers[0], 'localhost:' + servers[0].port + ' peertube-plugin-test-four', 1, false) | ||
54 | await waitUntilLog(servers[0], 'Hello world from plugin four', 1) | ||
55 | }) | ||
56 | }) | ||
57 | |||
58 | describe('Database', function () { | ||
59 | |||
60 | it('Should have made a query', async function () { | ||
61 | await waitUntilLog(servers[0], `root email is admin${servers[0].internalServerNumber}@example.com`) | ||
62 | }) | ||
63 | }) | ||
64 | |||
65 | describe('Config', function () { | ||
66 | |||
67 | it('Should have the correct webserver url', async function () { | ||
68 | await waitUntilLog(servers[0], `server url is http://localhost:${servers[0].port}`) | ||
69 | }) | ||
70 | }) | ||
71 | |||
72 | describe('Server', function () { | ||
73 | |||
74 | it('Should get the server actor', async function () { | ||
75 | await waitUntilLog(servers[0], 'server actor name is peertube') | ||
76 | }) | ||
77 | }) | ||
78 | |||
79 | describe('Moderation', function () { | ||
80 | let videoUUIDServer1: string | ||
81 | |||
82 | before(async function () { | ||
83 | this.timeout(15000) | ||
84 | |||
85 | { | ||
86 | const res = await uploadVideoAndGetId({ server: servers[0], videoName: 'video server 1' }) | ||
87 | videoUUIDServer1 = res.uuid | ||
88 | } | ||
89 | |||
90 | { | ||
91 | await uploadVideoAndGetId({ server: servers[1], videoName: 'video server 2' }) | ||
92 | } | ||
93 | |||
94 | await waitJobs(servers) | ||
95 | |||
96 | const res = await getVideosList(servers[0].url) | ||
97 | const videos = res.body.data | ||
98 | |||
99 | expect(videos).to.have.lengthOf(2) | ||
100 | }) | ||
101 | |||
102 | it('Should mute server 2', async function () { | ||
103 | this.timeout(10000) | ||
104 | await postCommand(servers[0], 'blockServer', { hostToBlock: `localhost:${servers[1].port}` }) | ||
105 | |||
106 | const res = await getVideosList(servers[0].url) | ||
107 | const videos = res.body.data | ||
108 | |||
109 | expect(videos).to.have.lengthOf(1) | ||
110 | expect(videos[0].name).to.equal('video server 1') | ||
111 | }) | ||
112 | |||
113 | it('Should unmute server 2', async function () { | ||
114 | await postCommand(servers[0], 'unblockServer', { hostToUnblock: `localhost:${servers[1].port}` }) | ||
115 | |||
116 | const res = await getVideosList(servers[0].url) | ||
117 | const videos = res.body.data | ||
118 | |||
119 | expect(videos).to.have.lengthOf(2) | ||
120 | }) | ||
121 | |||
122 | it('Should mute account of server 2', async function () { | ||
123 | await postCommand(servers[0], 'blockAccount', { handleToBlock: `root@localhost:${servers[1].port}` }) | ||
124 | |||
125 | const res = await getVideosList(servers[0].url) | ||
126 | const videos = res.body.data | ||
127 | |||
128 | expect(videos).to.have.lengthOf(1) | ||
129 | expect(videos[0].name).to.equal('video server 1') | ||
130 | }) | ||
131 | |||
132 | it('Should unmute account of server 2', async function () { | ||
133 | await postCommand(servers[0], 'unblockAccount', { handleToUnblock: `root@localhost:${servers[1].port}` }) | ||
134 | |||
135 | const res = await getVideosList(servers[0].url) | ||
136 | const videos = res.body.data | ||
137 | |||
138 | expect(videos).to.have.lengthOf(2) | ||
139 | }) | ||
140 | |||
141 | it('Should blacklist video', async function () { | ||
142 | this.timeout(10000) | ||
143 | |||
144 | await postCommand(servers[0], 'blacklist', { videoUUID: videoUUIDServer1, unfederate: true }) | ||
145 | |||
146 | await waitJobs(servers) | ||
147 | |||
148 | for (const server of servers) { | ||
149 | const res = await getVideosList(server.url) | ||
150 | const videos = res.body.data | ||
151 | |||
152 | expect(videos).to.have.lengthOf(1) | ||
153 | expect(videos[0].name).to.equal('video server 2') | ||
154 | } | ||
155 | }) | ||
156 | |||
157 | it('Should unblacklist video', async function () { | ||
158 | this.timeout(10000) | ||
159 | |||
160 | await postCommand(servers[0], 'unblacklist', { videoUUID: videoUUIDServer1 }) | ||
161 | |||
162 | await waitJobs(servers) | ||
163 | |||
164 | for (const server of servers) { | ||
165 | const res = await getVideosList(server.url) | ||
166 | const videos = res.body.data | ||
167 | |||
168 | expect(videos).to.have.lengthOf(2) | ||
169 | } | ||
170 | }) | ||
171 | }) | ||
172 | |||
173 | describe('Videos', function () { | ||
174 | let videoUUID: string | ||
175 | |||
176 | before(async () => { | ||
177 | const res = await uploadVideoAndGetId({ server: servers[0], videoName: 'video1' }) | ||
178 | videoUUID = res.uuid | ||
179 | }) | ||
180 | |||
181 | it('Should remove a video after a view', async function () { | ||
182 | this.timeout(20000) | ||
183 | |||
184 | // Should not throw -> video exists | ||
185 | await getVideo(servers[0].url, videoUUID) | ||
186 | // Should delete the video | ||
187 | await viewVideo(servers[0].url, videoUUID) | ||
188 | |||
189 | await waitUntilLog(servers[0], 'Video deleted by plugin four.') | ||
190 | |||
191 | try { | ||
192 | // Should throw because the video should have been deleted | ||
193 | await getVideo(servers[0].url, videoUUID) | ||
194 | throw new Error('Video exists') | ||
195 | } catch (err) { | ||
196 | if (err.message.includes('exists')) throw err | ||
197 | } | ||
198 | |||
199 | await checkVideoFilesWereRemoved(videoUUID, servers[0].internalServerNumber) | ||
200 | }) | ||
201 | |||
202 | it('Should have fetched the video by URL', async function () { | ||
203 | await waitUntilLog(servers[0], `video from DB uuid is ${videoUUID}`) | ||
204 | }) | ||
205 | }) | ||
206 | |||
207 | after(async function () { | ||
208 | await cleanupTests(servers) | ||
209 | }) | ||
210 | }) | ||
diff --git a/server/tests/plugins/plugin-router.ts b/server/tests/plugins/plugin-router.ts new file mode 100644 index 000000000..cf4130f4b --- /dev/null +++ b/server/tests/plugins/plugin-router.ts | |||
@@ -0,0 +1,91 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' | ||
5 | import { | ||
6 | getPluginTestPath, | ||
7 | installPlugin, | ||
8 | makeGetRequest, | ||
9 | makePostBodyRequest, | ||
10 | setAccessTokensToServers, uninstallPlugin | ||
11 | } from '../../../shared/extra-utils' | ||
12 | import { expect } from 'chai' | ||
13 | |||
14 | describe('Test plugin helpers', function () { | ||
15 | let server: ServerInfo | ||
16 | const basePaths = [ | ||
17 | '/plugins/test-five/router/', | ||
18 | '/plugins/test-five/0.0.1/router/' | ||
19 | ] | ||
20 | |||
21 | before(async function () { | ||
22 | this.timeout(30000) | ||
23 | |||
24 | server = await flushAndRunServer(1) | ||
25 | await setAccessTokensToServers([ server ]) | ||
26 | |||
27 | await installPlugin({ | ||
28 | url: server.url, | ||
29 | accessToken: server.accessToken, | ||
30 | path: getPluginTestPath('-five') | ||
31 | }) | ||
32 | }) | ||
33 | |||
34 | it('Should answer "pong"', async function () { | ||
35 | for (const path of basePaths) { | ||
36 | const res = await makeGetRequest({ | ||
37 | url: server.url, | ||
38 | path: path + 'ping', | ||
39 | statusCodeExpected: 200 | ||
40 | }) | ||
41 | |||
42 | expect(res.body.message).to.equal('pong') | ||
43 | } | ||
44 | }) | ||
45 | |||
46 | it('Should mirror post body', async function () { | ||
47 | const body = { | ||
48 | hello: 'world', | ||
49 | riri: 'fifi', | ||
50 | loulou: 'picsou' | ||
51 | } | ||
52 | |||
53 | for (const path of basePaths) { | ||
54 | const res = await makePostBodyRequest({ | ||
55 | url: server.url, | ||
56 | path: path + 'form/post/mirror', | ||
57 | fields: body, | ||
58 | statusCodeExpected: 200 | ||
59 | }) | ||
60 | |||
61 | expect(res.body).to.deep.equal(body) | ||
62 | } | ||
63 | }) | ||
64 | |||
65 | it('Should remove the plugin and remove the routes', async function () { | ||
66 | await uninstallPlugin({ | ||
67 | url: server.url, | ||
68 | accessToken: server.accessToken, | ||
69 | npmName: 'peertube-plugin-test-five' | ||
70 | }) | ||
71 | |||
72 | for (const path of basePaths) { | ||
73 | await makeGetRequest({ | ||
74 | url: server.url, | ||
75 | path: path + 'ping', | ||
76 | statusCodeExpected: 404 | ||
77 | }) | ||
78 | |||
79 | await makePostBodyRequest({ | ||
80 | url: server.url, | ||
81 | path: path + 'ping', | ||
82 | fields: {}, | ||
83 | statusCodeExpected: 404 | ||
84 | }) | ||
85 | } | ||
86 | }) | ||
87 | |||
88 | after(async function () { | ||
89 | await cleanupTests([ server ]) | ||
90 | }) | ||
91 | }) | ||
diff --git a/server/tests/plugins/plugin-storage.ts b/server/tests/plugins/plugin-storage.ts new file mode 100644 index 000000000..356692eb9 --- /dev/null +++ b/server/tests/plugins/plugin-storage.ts | |||
@@ -0,0 +1,30 @@ | |||
1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { getPluginTestPath, installPlugin, setAccessTokensToServers } from '../../../shared/extra-utils' | ||
5 | import { cleanupTests, flushAndRunServer, ServerInfo, waitUntilLog } from '../../../shared/extra-utils/server/servers' | ||
6 | |||
7 | describe('Test plugin storage', function () { | ||
8 | let server: ServerInfo | ||
9 | |||
10 | before(async function () { | ||
11 | this.timeout(30000) | ||
12 | |||
13 | server = await flushAndRunServer(1) | ||
14 | await setAccessTokensToServers([ server ]) | ||
15 | |||
16 | await installPlugin({ | ||
17 | url: server.url, | ||
18 | accessToken: server.accessToken, | ||
19 | path: getPluginTestPath('-six') | ||
20 | }) | ||
21 | }) | ||
22 | |||
23 | it('Should correctly store a subkey', async function () { | ||
24 | await waitUntilLog(server, 'superkey stored value is toto') | ||
25 | }) | ||
26 | |||
27 | after(async function () { | ||
28 | await cleanupTests([ server ]) | ||
29 | }) | ||
30 | }) | ||
diff --git a/server/tests/plugins/translations.ts b/server/tests/plugins/translations.ts index 88d91a033..8dc2043b8 100644 --- a/server/tests/plugins/translations.ts +++ b/server/tests/plugins/translations.ts | |||
@@ -1,38 +1,15 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' | ||
5 | import { | 6 | import { |
6 | cleanupTests, | ||
7 | flushAndRunMultipleServers, | ||
8 | flushAndRunServer, killallServers, reRunServer, | ||
9 | ServerInfo, | ||
10 | waitUntilLog | ||
11 | } from '../../../shared/extra-utils/server/servers' | ||
12 | import { | ||
13 | addVideoCommentReply, | ||
14 | addVideoCommentThread, | ||
15 | deleteVideoComment, | ||
16 | getPluginTestPath, | 7 | getPluginTestPath, |
17 | getVideosList, | 8 | getPluginTranslations, |
18 | installPlugin, | 9 | installPlugin, |
19 | removeVideo, | ||
20 | setAccessTokensToServers, | 10 | setAccessTokensToServers, |
21 | updateVideo, | 11 | uninstallPlugin |
22 | uploadVideo, | ||
23 | viewVideo, | ||
24 | getVideosListPagination, | ||
25 | getVideo, | ||
26 | getVideoCommentThreads, | ||
27 | getVideoThreadComments, | ||
28 | getVideoWithToken, | ||
29 | setDefaultVideoChannel, | ||
30 | waitJobs, | ||
31 | doubleFollow, getVideoLanguages, getVideoLicences, getVideoCategories, uninstallPlugin, getPluginTranslations | ||
32 | } from '../../../shared/extra-utils' | 12 | } from '../../../shared/extra-utils' |
33 | import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' | ||
34 | import { VideoDetails } from '../../../shared/models/videos' | ||
35 | import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports' | ||
36 | 13 | ||
37 | const expect = chai.expect | 14 | const expect = chai.expect |
38 | 15 | ||
@@ -69,7 +46,7 @@ describe('Test plugin translations', function () { | |||
69 | 46 | ||
70 | expect(res.body).to.deep.equal({ | 47 | expect(res.body).to.deep.equal({ |
71 | 'peertube-plugin-test': { | 48 | 'peertube-plugin-test': { |
72 | 'Hi': 'Coucou' | 49 | Hi: 'Coucou' |
73 | }, | 50 | }, |
74 | 'peertube-plugin-test-two': { | 51 | 'peertube-plugin-test-two': { |
75 | 'Hello world': 'Bonjour le monde' | 52 | 'Hello world': 'Bonjour le monde' |
@@ -95,7 +72,7 @@ describe('Test plugin translations', function () { | |||
95 | 72 | ||
96 | expect(res.body).to.deep.equal({ | 73 | expect(res.body).to.deep.equal({ |
97 | 'peertube-plugin-test': { | 74 | 'peertube-plugin-test': { |
98 | 'Hi': 'Coucou' | 75 | Hi: 'Coucou' |
99 | } | 76 | } |
100 | }) | 77 | }) |
101 | } | 78 | } |
diff --git a/server/tests/plugins/video-constants.ts b/server/tests/plugins/video-constants.ts index 6562e2b45..fec9196e2 100644 --- a/server/tests/plugins/video-constants.ts +++ b/server/tests/plugins/video-constants.ts | |||
@@ -1,38 +1,21 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* eslint-disable @typescript-eslint/no-unused-expressions,@typescript-eslint/require-await */ |
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../shared/extra-utils/server/servers' | ||
5 | import { | 6 | import { |
6 | cleanupTests, | 7 | createVideoPlaylist, |
7 | flushAndRunMultipleServers, | ||
8 | flushAndRunServer, killallServers, reRunServer, | ||
9 | ServerInfo, | ||
10 | waitUntilLog | ||
11 | } from '../../../shared/extra-utils/server/servers' | ||
12 | import { | ||
13 | addVideoCommentReply, | ||
14 | addVideoCommentThread, | ||
15 | deleteVideoComment, | ||
16 | getPluginTestPath, | 8 | getPluginTestPath, |
17 | getVideosList, | 9 | getVideo, |
10 | getVideoCategories, | ||
11 | getVideoLanguages, | ||
12 | getVideoLicences, getVideoPlaylistPrivacies, getVideoPrivacies, | ||
18 | installPlugin, | 13 | installPlugin, |
19 | removeVideo, | ||
20 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
21 | updateVideo, | 15 | uninstallPlugin, |
22 | uploadVideo, | 16 | uploadVideo |
23 | viewVideo, | ||
24 | getVideosListPagination, | ||
25 | getVideo, | ||
26 | getVideoCommentThreads, | ||
27 | getVideoThreadComments, | ||
28 | getVideoWithToken, | ||
29 | setDefaultVideoChannel, | ||
30 | waitJobs, | ||
31 | doubleFollow, getVideoLanguages, getVideoLicences, getVideoCategories, uninstallPlugin | ||
32 | } from '../../../shared/extra-utils' | 17 | } from '../../../shared/extra-utils' |
33 | import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model' | 18 | import { VideoDetails, VideoPlaylistPrivacy } from '../../../shared/models/videos' |
34 | import { VideoDetails } from '../../../shared/models/videos' | ||
35 | import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports' | ||
36 | 19 | ||
37 | const expect = chai.expect | 20 | const expect = chai.expect |
38 | 21 | ||
@@ -85,6 +68,35 @@ describe('Test plugin altering video constants', function () { | |||
85 | expect(licences[43]).to.equal('High best licence') | 68 | expect(licences[43]).to.equal('High best licence') |
86 | }) | 69 | }) |
87 | 70 | ||
71 | it('Should have updated video privacies', async function () { | ||
72 | const res = await getVideoPrivacies(server.url) | ||
73 | const privacies = res.body | ||
74 | |||
75 | expect(privacies[1]).to.exist | ||
76 | expect(privacies[2]).to.not.exist | ||
77 | expect(privacies[3]).to.exist | ||
78 | expect(privacies[4]).to.exist | ||
79 | }) | ||
80 | |||
81 | it('Should have updated playlist privacies', async function () { | ||
82 | const res = await getVideoPlaylistPrivacies(server.url) | ||
83 | const playlistPrivacies = res.body | ||
84 | |||
85 | expect(playlistPrivacies[1]).to.exist | ||
86 | expect(playlistPrivacies[2]).to.exist | ||
87 | expect(playlistPrivacies[3]).to.not.exist | ||
88 | }) | ||
89 | |||
90 | it('Should not be able to create a video with this privacy', async function () { | ||
91 | const attrs = { name: 'video', privacy: 2 } | ||
92 | await uploadVideo(server.url, server.accessToken, attrs, 400) | ||
93 | }) | ||
94 | |||
95 | it('Should not be able to create a video with this privacy', async function () { | ||
96 | const attrs = { displayName: 'video playlist', privacy: VideoPlaylistPrivacy.PRIVATE } | ||
97 | await createVideoPlaylist({ url: server.url, token: server.accessToken, playlistAttrs: attrs, expectedStatus: 400 }) | ||
98 | }) | ||
99 | |||
88 | it('Should be able to upload a video with these values', async function () { | 100 | it('Should be able to upload a video with these values', async function () { |
89 | const attrs = { name: 'video', category: 42, licence: 42, language: 'al_bhed2' } | 101 | const attrs = { name: 'video', category: 42, licence: 42, language: 'al_bhed2' } |
90 | const resUpload = await uploadVideo(server.url, server.accessToken, attrs) | 102 | const resUpload = await uploadVideo(server.url, server.accessToken, attrs) |
@@ -97,40 +109,59 @@ describe('Test plugin altering video constants', function () { | |||
97 | expect(video.category.label).to.equal('Best category') | 109 | expect(video.category.label).to.equal('Best category') |
98 | }) | 110 | }) |
99 | 111 | ||
100 | it('Should uninstall the plugin and reset languages, categories and licences', async function () { | 112 | it('Should uninstall the plugin and reset languages, categories, licences and privacies', async function () { |
101 | await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-test-three' }) | 113 | await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-test-three' }) |
102 | 114 | ||
103 | { | 115 | { |
104 | const res = await getVideoLanguages(server.url) | 116 | const res = await getVideoLanguages(server.url) |
105 | const languages = res.body | 117 | const languages = res.body |
106 | 118 | ||
107 | expect(languages[ 'en' ]).to.equal('English') | 119 | expect(languages['en']).to.equal('English') |
108 | expect(languages[ 'fr' ]).to.equal('French') | 120 | expect(languages['fr']).to.equal('French') |
109 | 121 | ||
110 | expect(languages[ 'al_bhed' ]).to.not.exist | 122 | expect(languages['al_bhed']).to.not.exist |
111 | expect(languages[ 'al_bhed2' ]).to.not.exist | 123 | expect(languages['al_bhed2']).to.not.exist |
112 | } | 124 | } |
113 | 125 | ||
114 | { | 126 | { |
115 | const res = await getVideoCategories(server.url) | 127 | const res = await getVideoCategories(server.url) |
116 | const categories = res.body | 128 | const categories = res.body |
117 | 129 | ||
118 | expect(categories[ 1 ]).to.equal('Music') | 130 | expect(categories[1]).to.equal('Music') |
119 | expect(categories[ 2 ]).to.equal('Films') | 131 | expect(categories[2]).to.equal('Films') |
120 | 132 | ||
121 | expect(categories[ 42 ]).to.not.exist | 133 | expect(categories[42]).to.not.exist |
122 | expect(categories[ 43 ]).to.not.exist | 134 | expect(categories[43]).to.not.exist |
123 | } | 135 | } |
124 | 136 | ||
125 | { | 137 | { |
126 | const res = await getVideoLicences(server.url) | 138 | const res = await getVideoLicences(server.url) |
127 | const licences = res.body | 139 | const licences = res.body |
128 | 140 | ||
129 | expect(licences[ 1 ]).to.equal('Attribution') | 141 | expect(licences[1]).to.equal('Attribution') |
130 | expect(licences[ 7 ]).to.equal('Public Domain Dedication') | 142 | expect(licences[7]).to.equal('Public Domain Dedication') |
143 | |||
144 | expect(licences[42]).to.not.exist | ||
145 | expect(licences[43]).to.not.exist | ||
146 | } | ||
147 | |||
148 | { | ||
149 | const res = await getVideoPrivacies(server.url) | ||
150 | const privacies = res.body | ||
151 | |||
152 | expect(privacies[1]).to.exist | ||
153 | expect(privacies[2]).to.exist | ||
154 | expect(privacies[3]).to.exist | ||
155 | expect(privacies[4]).to.exist | ||
156 | } | ||
157 | |||
158 | { | ||
159 | const res = await getVideoPlaylistPrivacies(server.url) | ||
160 | const playlistPrivacies = res.body | ||
131 | 161 | ||
132 | expect(licences[ 42 ]).to.not.exist | 162 | expect(playlistPrivacies[1]).to.exist |
133 | expect(licences[ 43 ]).to.not.exist | 163 | expect(playlistPrivacies[2]).to.exist |
164 | expect(playlistPrivacies[3]).to.exist | ||
134 | } | 165 | } |
135 | }) | 166 | }) |
136 | 167 | ||
diff --git a/server/tests/real-world/populate-database.ts b/server/tests/real-world/populate-database.ts deleted file mode 100644 index b1c1688e7..000000000 --- a/server/tests/real-world/populate-database.ts +++ /dev/null | |||
@@ -1,122 +0,0 @@ | |||
1 | import { VideoRateType } from '../../../shared' | ||
2 | import { | ||
3 | addVideoChannel, | ||
4 | createUser, | ||
5 | flushTests, | ||
6 | getVideosList, | ||
7 | killallServers, | ||
8 | rateVideo, | ||
9 | flushAndRunServer, | ||
10 | ServerInfo, | ||
11 | setAccessTokensToServers, | ||
12 | uploadVideo | ||
13 | } from '../../../shared/extra-utils' | ||
14 | import * as Bluebird from 'bluebird' | ||
15 | |||
16 | start() | ||
17 | .catch(err => console.error(err)) | ||
18 | |||
19 | // ---------------------------------------------------------------------------- | ||
20 | |||
21 | async function start () { | ||
22 | |||
23 | console.log('Flushed tests.') | ||
24 | |||
25 | const server = await flushAndRunServer(6) | ||
26 | |||
27 | process.on('exit', async () => { | ||
28 | killallServers([ server ]) | ||
29 | return | ||
30 | }) | ||
31 | process.on('SIGINT', goodbye) | ||
32 | process.on('SIGTERM', goodbye) | ||
33 | |||
34 | await setAccessTokensToServers([ server ]) | ||
35 | |||
36 | console.log('Servers ran.') | ||
37 | |||
38 | // Forever | ||
39 | const fakeTab = Array.from(Array(1000000).keys()) | ||
40 | const funs = [ | ||
41 | uploadCustom | ||
42 | // uploadCustom, | ||
43 | // uploadCustom, | ||
44 | // uploadCustom, | ||
45 | // likeCustom, | ||
46 | // createUserCustom, | ||
47 | // createCustomChannel | ||
48 | ] | ||
49 | const promises = [] | ||
50 | |||
51 | for (const fun of funs) { | ||
52 | promises.push( | ||
53 | Bluebird.map(fakeTab, () => { | ||
54 | return fun(server).catch(err => console.error(err)) | ||
55 | }, { concurrency: 3 }) | ||
56 | ) | ||
57 | } | ||
58 | |||
59 | await Promise.all(promises) | ||
60 | } | ||
61 | |||
62 | function getRandomInt (min, max) { | ||
63 | return Math.floor(Math.random() * (max - min)) + min | ||
64 | } | ||
65 | |||
66 | function createCustomChannel (server: ServerInfo) { | ||
67 | const videoChannel = { | ||
68 | name: Date.now().toString(), | ||
69 | displayName: Date.now().toString(), | ||
70 | description: Date.now().toString() | ||
71 | } | ||
72 | |||
73 | return addVideoChannel(server.url, server.accessToken, videoChannel) | ||
74 | } | ||
75 | |||
76 | function createUserCustom (server: ServerInfo) { | ||
77 | const username = Date.now().toString() + getRandomInt(0, 100000) | ||
78 | console.log('Creating user %s.', username) | ||
79 | |||
80 | return createUser({ url: server.url, accessToken: server.accessToken, username: username, password: 'coucou' }) | ||
81 | } | ||
82 | |||
83 | function uploadCustom (server: ServerInfo) { | ||
84 | console.log('Uploading video.') | ||
85 | |||
86 | const videoAttributes = { | ||
87 | name: Date.now() + ' name', | ||
88 | category: 4, | ||
89 | nsfw: false, | ||
90 | licence: 2, | ||
91 | language: 'en', | ||
92 | description: Date.now() + ' description', | ||
93 | tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ], | ||
94 | fixture: 'video_short.mp4' | ||
95 | } | ||
96 | |||
97 | return uploadVideo(server.url, server.accessToken, videoAttributes) | ||
98 | } | ||
99 | |||
100 | function likeCustom (server: ServerInfo) { | ||
101 | return rateCustom(server, 'like') | ||
102 | } | ||
103 | |||
104 | function dislikeCustom (server: ServerInfo) { | ||
105 | return rateCustom(server, 'dislike') | ||
106 | } | ||
107 | |||
108 | async function rateCustom (server: ServerInfo, rating: VideoRateType) { | ||
109 | const res = await getVideosList(server.url) | ||
110 | |||
111 | const videos = res.body.data | ||
112 | if (videos.length === 0) return undefined | ||
113 | |||
114 | const videoToRate = videos[getRandomInt(0, videos.length)] | ||
115 | |||
116 | console.log('Rating (%s) video.', rating) | ||
117 | return rateVideo(server.url, server.accessToken, videoToRate.id, rating) | ||
118 | } | ||
119 | |||
120 | function goodbye () { | ||
121 | return process.exit(-1) | ||
122 | } | ||
diff --git a/server/tests/real-world/real-world.ts b/server/tests/real-world/real-world.ts deleted file mode 100644 index cba5ac311..000000000 --- a/server/tests/real-world/real-world.ts +++ /dev/null | |||
@@ -1,375 +0,0 @@ | |||
1 | // /!\ Before imports /!\ | ||
2 | process.env.NODE_ENV = 'test' | ||
3 | |||
4 | import * as program from 'commander' | ||
5 | import { Video, VideoFile, VideoRateType } from '../../../shared' | ||
6 | import { JobState } from '../../../shared/models' | ||
7 | import { | ||
8 | flushAndRunMultipleServers, | ||
9 | flushTests, follow, | ||
10 | getVideo, | ||
11 | getVideosList, getVideosListPagination, | ||
12 | killallServers, | ||
13 | removeVideo, | ||
14 | ServerInfo as DefaultServerInfo, | ||
15 | setAccessTokensToServers, | ||
16 | updateVideo, | ||
17 | uploadVideo, viewVideo, | ||
18 | wait | ||
19 | } from '../../../shared/extra-utils' | ||
20 | import { getJobsListPaginationAndSort } from '../../../shared/extra-utils/server/jobs' | ||
21 | |||
22 | interface ServerInfo extends DefaultServerInfo { | ||
23 | requestsNumber: number | ||
24 | } | ||
25 | |||
26 | program | ||
27 | .option('-c, --create [weight]', 'Weight for creating videos') | ||
28 | .option('-r, --remove [weight]', 'Weight for removing videos') | ||
29 | .option('-u, --update [weight]', 'Weight for updating videos') | ||
30 | .option('-v, --view [weight]', 'Weight for viewing videos') | ||
31 | .option('-l, --like [weight]', 'Weight for liking videos') | ||
32 | .option('-s, --dislike [weight]', 'Weight for disliking videos') | ||
33 | .option('-p, --servers [n]', 'Number of servers to run (3 or 6)', /^3|6$/, 3) | ||
34 | .option('-i, --interval-action [interval]', 'Interval in ms for an action') | ||
35 | .option('-I, --interval-integrity [interval]', 'Interval in ms for an integrity check') | ||
36 | .option('-f, --flush', 'Flush data on exit') | ||
37 | .option('-d, --difference', 'Display difference if integrity is not okay') | ||
38 | .parse(process.argv) | ||
39 | |||
40 | const createWeight = program['create'] !== undefined ? parseInt(program['create'], 10) : 5 | ||
41 | const removeWeight = program['remove'] !== undefined ? parseInt(program['remove'], 10) : 4 | ||
42 | const updateWeight = program['update'] !== undefined ? parseInt(program['update'], 10) : 4 | ||
43 | const viewWeight = program['view'] !== undefined ? parseInt(program['view'], 10) : 4 | ||
44 | const likeWeight = program['like'] !== undefined ? parseInt(program['like'], 10) : 4 | ||
45 | const dislikeWeight = program['dislike'] !== undefined ? parseInt(program['dislike'], 10) : 4 | ||
46 | const flushAtExit = program['flush'] || false | ||
47 | const actionInterval = program['intervalAction'] !== undefined ? parseInt(program['intervalAction'], 10) : 500 | ||
48 | const integrityInterval = program['intervalIntegrity'] !== undefined ? parseInt(program['intervalIntegrity'], 10) : 60000 | ||
49 | const displayDiffOnFail = program['difference'] || false | ||
50 | |||
51 | const numberOfServers = 6 | ||
52 | |||
53 | console.log( | ||
54 | 'Create weight: %d, update weight: %d, remove weight: %d, view weight: %d, like weight: %d, dislike weight: %d.', | ||
55 | createWeight, updateWeight, removeWeight, viewWeight, likeWeight, dislikeWeight | ||
56 | ) | ||
57 | |||
58 | if (flushAtExit) { | ||
59 | console.log('Program will flush data on exit.') | ||
60 | } else { | ||
61 | console.log('Program will not flush data on exit.') | ||
62 | } | ||
63 | if (displayDiffOnFail) { | ||
64 | console.log('Program will display diff on failure.') | ||
65 | } else { | ||
66 | console.log('Program will not display diff on failure') | ||
67 | } | ||
68 | console.log('Interval in ms for each action: %d.', actionInterval) | ||
69 | console.log('Interval in ms for each integrity check: %d.', integrityInterval) | ||
70 | |||
71 | console.log('Run servers...') | ||
72 | |||
73 | start() | ||
74 | |||
75 | // ---------------------------------------------------------------------------- | ||
76 | |||
77 | async function start () { | ||
78 | const servers = await runServers(numberOfServers) | ||
79 | |||
80 | process.on('exit', async () => { | ||
81 | await exitServers(servers, flushAtExit) | ||
82 | |||
83 | return | ||
84 | }) | ||
85 | process.on('SIGINT', goodbye) | ||
86 | process.on('SIGTERM', goodbye) | ||
87 | |||
88 | console.log('Servers ran') | ||
89 | initializeRequestsPerServer(servers) | ||
90 | |||
91 | let checking = false | ||
92 | |||
93 | setInterval(async () => { | ||
94 | if (checking === true) return | ||
95 | |||
96 | const rand = getRandomInt(0, createWeight + updateWeight + removeWeight + viewWeight + likeWeight + dislikeWeight) | ||
97 | |||
98 | const numServer = getRandomNumServer(servers) | ||
99 | servers[numServer].requestsNumber++ | ||
100 | |||
101 | if (rand < createWeight) { | ||
102 | await upload(servers, numServer) | ||
103 | } else if (rand < createWeight + updateWeight) { | ||
104 | await update(servers, numServer) | ||
105 | } else if (rand < createWeight + updateWeight + removeWeight) { | ||
106 | await remove(servers, numServer) | ||
107 | } else if (rand < createWeight + updateWeight + removeWeight + viewWeight) { | ||
108 | await view(servers, numServer) | ||
109 | } else if (rand < createWeight + updateWeight + removeWeight + viewWeight + likeWeight) { | ||
110 | await like(servers, numServer) | ||
111 | } else { | ||
112 | await dislike(servers, numServer) | ||
113 | } | ||
114 | }, actionInterval) | ||
115 | |||
116 | // The function will check the consistency between servers (should have the same videos with same attributes...) | ||
117 | setInterval(function () { | ||
118 | if (checking === true) return | ||
119 | |||
120 | console.log('Checking integrity...') | ||
121 | checking = true | ||
122 | |||
123 | const waitingInterval = setInterval(async () => { | ||
124 | const pendingRequests = await isTherePendingRequests(servers) | ||
125 | if (pendingRequests === true) { | ||
126 | console.log('A server has pending requests, waiting...') | ||
127 | return | ||
128 | } | ||
129 | |||
130 | // Even if there are no pending request, wait some potential processes | ||
131 | await wait(2000) | ||
132 | await checkIntegrity(servers) | ||
133 | |||
134 | initializeRequestsPerServer(servers) | ||
135 | checking = false | ||
136 | clearInterval(waitingInterval) | ||
137 | }, 10000) | ||
138 | }, integrityInterval) | ||
139 | } | ||
140 | |||
141 | function initializeRequestsPerServer (servers: ServerInfo[]) { | ||
142 | servers.forEach(server => server.requestsNumber = 0) | ||
143 | } | ||
144 | |||
145 | function getRandomInt (min, max) { | ||
146 | return Math.floor(Math.random() * (max - min)) + min | ||
147 | } | ||
148 | |||
149 | function getRandomNumServer (servers) { | ||
150 | return getRandomInt(0, servers.length) | ||
151 | } | ||
152 | |||
153 | async function runServers (numberOfServers: number) { | ||
154 | const servers: ServerInfo[] = (await flushAndRunMultipleServers(numberOfServers)) | ||
155 | .map(s => Object.assign({ requestsNumber: 0 }, s)) | ||
156 | |||
157 | // Get the access tokens | ||
158 | await setAccessTokensToServers(servers) | ||
159 | |||
160 | for (let i = 0; i < numberOfServers; i++) { | ||
161 | for (let j = 0; j < numberOfServers; j++) { | ||
162 | if (i === j) continue | ||
163 | |||
164 | await follow(servers[i].url, [ servers[j].url ], servers[i].accessToken) | ||
165 | } | ||
166 | } | ||
167 | |||
168 | return servers | ||
169 | } | ||
170 | |||
171 | async function exitServers (servers: ServerInfo[], flushAtExit: boolean) { | ||
172 | killallServers(servers) | ||
173 | |||
174 | if (flushAtExit) await flushTests() | ||
175 | } | ||
176 | |||
177 | function upload (servers: ServerInfo[], numServer: number) { | ||
178 | console.log('Uploading video to server ' + numServer) | ||
179 | |||
180 | const videoAttributes = { | ||
181 | name: Date.now() + ' name', | ||
182 | category: 4, | ||
183 | nsfw: false, | ||
184 | licence: 2, | ||
185 | language: 'en', | ||
186 | description: Date.now() + ' description', | ||
187 | tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ], | ||
188 | fixture: 'video_short1.webm' | ||
189 | } | ||
190 | return uploadVideo(servers[numServer].url, servers[numServer].accessToken, videoAttributes) | ||
191 | } | ||
192 | |||
193 | async function update (servers: ServerInfo[], numServer: number) { | ||
194 | const res = await getVideosList(servers[numServer].url) | ||
195 | |||
196 | const videos = res.body.data.filter(video => video.isLocal === true) | ||
197 | if (videos.length === 0) return undefined | ||
198 | |||
199 | const toUpdate = videos[getRandomInt(0, videos.length)].id | ||
200 | const attributes = { | ||
201 | name: Date.now() + ' name', | ||
202 | description: Date.now() + ' description', | ||
203 | tags: [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ] | ||
204 | } | ||
205 | |||
206 | console.log('Updating video of server ' + numServer) | ||
207 | |||
208 | return updateVideo(servers[numServer].url, servers[numServer].accessToken, toUpdate, attributes) | ||
209 | } | ||
210 | |||
211 | async function remove (servers: ServerInfo[], numServer: number) { | ||
212 | const res = await getVideosList(servers[numServer].url) | ||
213 | const videos = res.body.data.filter(video => video.isLocal === true) | ||
214 | if (videos.length === 0) return undefined | ||
215 | |||
216 | const toRemove = videos[getRandomInt(0, videos.length)].id | ||
217 | |||
218 | console.log('Removing video from server ' + numServer) | ||
219 | return removeVideo(servers[numServer].url, servers[numServer].accessToken, toRemove) | ||
220 | } | ||
221 | |||
222 | async function view (servers: ServerInfo[], numServer: number) { | ||
223 | const res = await getVideosList(servers[numServer].url) | ||
224 | |||
225 | const videos = res.body.data | ||
226 | if (videos.length === 0) return undefined | ||
227 | |||
228 | const toView = videos[getRandomInt(0, videos.length)].id | ||
229 | |||
230 | console.log('Viewing video from server ' + numServer) | ||
231 | return viewVideo(servers[numServer].url, toView) | ||
232 | } | ||
233 | |||
234 | function like (servers: ServerInfo[], numServer: number) { | ||
235 | return rate(servers, numServer, 'like') | ||
236 | } | ||
237 | |||
238 | function dislike (servers: ServerInfo[], numServer: number) { | ||
239 | return rate(servers, numServer, 'dislike') | ||
240 | } | ||
241 | |||
242 | async function rate (servers: ServerInfo[], numServer: number, rating: VideoRateType) { | ||
243 | const res = await getVideosList(servers[numServer].url) | ||
244 | |||
245 | const videos = res.body.data | ||
246 | if (videos.length === 0) return undefined | ||
247 | |||
248 | const toRate = videos[getRandomInt(0, videos.length)].id | ||
249 | |||
250 | console.log('Rating (%s) video from server %d', rating, numServer) | ||
251 | return getVideo(servers[numServer].url, toRate) | ||
252 | } | ||
253 | |||
254 | async function checkIntegrity (servers: ServerInfo[]) { | ||
255 | const videos: Video[][] = [] | ||
256 | const tasks: Promise<any>[] = [] | ||
257 | |||
258 | // Fetch all videos and remove some fields that can differ between servers | ||
259 | for (const server of servers) { | ||
260 | const p = getVideosListPagination(server.url, 0, 1000000, '-createdAt') | ||
261 | .then(res => videos.push(res.body.data)) | ||
262 | tasks.push(p) | ||
263 | } | ||
264 | |||
265 | await Promise.all(tasks) | ||
266 | |||
267 | let i = 0 | ||
268 | for (const video of videos) { | ||
269 | const differences = areDifferences(video, videos[0]) | ||
270 | if (differences !== undefined) { | ||
271 | console.error('Integrity not ok with server %d!', i + 1) | ||
272 | |||
273 | if (displayDiffOnFail) { | ||
274 | console.log(differences) | ||
275 | } | ||
276 | |||
277 | process.exit(-1) | ||
278 | } | ||
279 | |||
280 | i++ | ||
281 | } | ||
282 | |||
283 | console.log('Integrity ok.') | ||
284 | } | ||
285 | |||
286 | function areDifferences (videos1: Video[], videos2: Video[]) { | ||
287 | // Remove some keys we don't want to compare | ||
288 | videos1.concat(videos2).forEach(video => { | ||
289 | delete video.id | ||
290 | delete video.isLocal | ||
291 | delete video.thumbnailPath | ||
292 | delete video.updatedAt | ||
293 | delete video.views | ||
294 | }) | ||
295 | |||
296 | if (videos1.length !== videos2.length) { | ||
297 | return `Videos length are different (${videos1.length}/${videos2.length}).` | ||
298 | } | ||
299 | |||
300 | for (const video1 of videos1) { | ||
301 | const video2 = videos2.find(video => video.uuid === video1.uuid) | ||
302 | |||
303 | if (!video2) return 'Video ' + video1.uuid + ' is missing.' | ||
304 | |||
305 | for (const videoKey of Object.keys(video1)) { | ||
306 | const attribute1 = video1[videoKey] | ||
307 | const attribute2 = video2[videoKey] | ||
308 | |||
309 | if (videoKey === 'tags') { | ||
310 | if (attribute1.length !== attribute2.length) { | ||
311 | return 'Tags are different.' | ||
312 | } | ||
313 | |||
314 | attribute1.forEach(tag1 => { | ||
315 | if (attribute2.indexOf(tag1) === -1) { | ||
316 | return 'Tag ' + tag1 + ' is missing.' | ||
317 | } | ||
318 | }) | ||
319 | } else if (videoKey === 'files') { | ||
320 | if (attribute1.length !== attribute2.length) { | ||
321 | return 'Video files are different.' | ||
322 | } | ||
323 | |||
324 | attribute1.forEach((videoFile1: VideoFile) => { | ||
325 | const videoFile2: VideoFile = attribute2.find(videoFile => videoFile.magnetUri === videoFile1.magnetUri) | ||
326 | if (!videoFile2) { | ||
327 | return `Video ${video1.uuid} has missing video file ${videoFile1.magnetUri}.` | ||
328 | } | ||
329 | |||
330 | if (videoFile1.size !== videoFile2.size || videoFile1.resolution.label !== videoFile2.resolution.label) { | ||
331 | return `Video ${video1.uuid} has different video file ${videoFile1.magnetUri}.` | ||
332 | } | ||
333 | }) | ||
334 | } else { | ||
335 | if (attribute1 !== attribute2) { | ||
336 | return `Video ${video1.uuid} has different value for attribute ${videoKey}.` | ||
337 | } | ||
338 | } | ||
339 | } | ||
340 | } | ||
341 | |||
342 | return undefined | ||
343 | } | ||
344 | |||
345 | function goodbye () { | ||
346 | return process.exit(-1) | ||
347 | } | ||
348 | |||
349 | async function isTherePendingRequests (servers: ServerInfo[]) { | ||
350 | const states: JobState[] = [ 'waiting', 'active', 'delayed' ] | ||
351 | const tasks: Promise<any>[] = [] | ||
352 | let pendingRequests = false | ||
353 | |||
354 | // Check if each server has pending request | ||
355 | for (const server of servers) { | ||
356 | for (const state of states) { | ||
357 | const p = getJobsListPaginationAndSort({ | ||
358 | url: server.url, | ||
359 | accessToken: server.accessToken, | ||
360 | state: state, | ||
361 | start: 0, | ||
362 | count: 10, | ||
363 | sort: '-createdAt' | ||
364 | }) | ||
365 | .then(res => { | ||
366 | if (res.body.total > 0) pendingRequests = true | ||
367 | }) | ||
368 | tasks.push(p) | ||
369 | } | ||
370 | } | ||
371 | |||
372 | await Promise.all(tasks) | ||
373 | |||
374 | return pendingRequests | ||
375 | } | ||
diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 58e2445ac..d5416fc38 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts | |||
@@ -3,9 +3,12 @@ import { getAppNumber, isTestInstance } from '../helpers/core-utils' | |||
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { root } from '../../shared/extra-utils/miscs/miscs' | 4 | import { root } from '../../shared/extra-utils/miscs/miscs' |
5 | import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' | 5 | import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' |
6 | import { Command } from 'commander' | 6 | import { CommanderStatic } from 'commander' |
7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' | 7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' |
8 | import { createLogger, format, transports } from 'winston' | 8 | import { createLogger, format, transports } from 'winston' |
9 | import { getMyUserInformation } from '@shared/extra-utils/users/users' | ||
10 | import { User, UserRole } from '@shared/models' | ||
11 | import { getAccessToken } from '@shared/extra-utils/users/login' | ||
9 | 12 | ||
10 | let configName = 'PeerTube/CLI' | 13 | let configName = 'PeerTube/CLI' |
11 | if (isTestInstance()) configName += `-${getAppNumber()}` | 14 | if (isTestInstance()) configName += `-${getAppNumber()}` |
@@ -14,24 +17,35 @@ const config = require('application-config')(configName) | |||
14 | 17 | ||
15 | const version = require('../../../package.json').version | 18 | const version = require('../../../package.json').version |
16 | 19 | ||
20 | async function getAdminTokenOrDie (url: string, username: string, password: string) { | ||
21 | const accessToken = await getAccessToken(url, username, password) | ||
22 | const resMe = await getMyUserInformation(url, accessToken) | ||
23 | const me: User = resMe.body | ||
24 | |||
25 | if (me.role !== UserRole.ADMINISTRATOR) { | ||
26 | console.error('You must be an administrator.') | ||
27 | process.exit(-1) | ||
28 | } | ||
29 | |||
30 | return accessToken | ||
31 | } | ||
32 | |||
17 | interface Settings { | 33 | interface Settings { |
18 | remotes: any[], | 34 | remotes: any[] |
19 | default: number | 35 | default: number |
20 | } | 36 | } |
21 | 37 | ||
22 | function getSettings () { | 38 | async function getSettings (): Promise<Settings> { |
23 | return new Promise<Settings>((res, rej) => { | 39 | const defaultSettings = { |
24 | const defaultSettings = { | 40 | remotes: [], |
25 | remotes: [], | 41 | default: -1 |
26 | default: -1 | 42 | } |
27 | } | ||
28 | 43 | ||
29 | config.read((err, data) => { | 44 | const data = await config.read() |
30 | if (err) return rej(err) | ||
31 | 45 | ||
32 | return res(Object.keys(data).length === 0 ? defaultSettings : data) | 46 | return Object.keys(data).length === 0 |
33 | }) | 47 | ? defaultSettings |
34 | }) | 48 | : data |
35 | } | 49 | } |
36 | 50 | ||
37 | async function getNetrc () { | 51 | async function getNetrc () { |
@@ -46,24 +60,12 @@ async function getNetrc () { | |||
46 | return netrc | 60 | return netrc |
47 | } | 61 | } |
48 | 62 | ||
49 | function writeSettings (settings) { | 63 | function writeSettings (settings: Settings) { |
50 | return new Promise((res, rej) => { | 64 | return config.write(settings) |
51 | config.write(settings, err => { | ||
52 | if (err) return rej(err) | ||
53 | |||
54 | return res() | ||
55 | }) | ||
56 | }) | ||
57 | } | 65 | } |
58 | 66 | ||
59 | function deleteSettings () { | 67 | function deleteSettings () { |
60 | return new Promise((res, rej) => { | 68 | return config.trash() |
61 | config.trash((err) => { | ||
62 | if (err) return rej(err) | ||
63 | |||
64 | return res() | ||
65 | }) | ||
66 | }) | ||
67 | } | 69 | } |
68 | 70 | ||
69 | function getRemoteObjectOrDie ( | 71 | function getRemoteObjectOrDie ( |
@@ -74,9 +76,9 @@ function getRemoteObjectOrDie ( | |||
74 | if (!program['url'] || !program['username'] || !program['password']) { | 76 | if (!program['url'] || !program['username'] || !program['password']) { |
75 | // No remote and we don't have program parameters: quit | 77 | // No remote and we don't have program parameters: quit |
76 | if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { | 78 | if (settings.remotes.length === 0 || Object.keys(netrc.machines).length === 0) { |
77 | if (!program[ 'url' ]) console.error('--url field is required.') | 79 | if (!program['url']) console.error('--url field is required.') |
78 | if (!program[ 'username' ]) console.error('--username field is required.') | 80 | if (!program['username']) console.error('--username field is required.') |
79 | if (!program[ 'password' ]) console.error('--password field is required.') | 81 | if (!program['password']) console.error('--password field is required.') |
80 | 82 | ||
81 | return process.exit(-1) | 83 | return process.exit(-1) |
82 | } | 84 | } |
@@ -96,13 +98,13 @@ function getRemoteObjectOrDie ( | |||
96 | } | 98 | } |
97 | 99 | ||
98 | return { | 100 | return { |
99 | url: program[ 'url' ], | 101 | url: program['url'], |
100 | username: program[ 'username' ], | 102 | username: program['username'], |
101 | password: program[ 'password' ] | 103 | password: program['password'] |
102 | } | 104 | } |
103 | } | 105 | } |
104 | 106 | ||
105 | function buildCommonVideoOptions (command: Command) { | 107 | function buildCommonVideoOptions (command: CommanderStatic) { |
106 | function list (val) { | 108 | function list (val) { |
107 | return val.split(',') | 109 | return val.split(',') |
108 | } | 110 | } |
@@ -117,13 +119,14 @@ function buildCommonVideoOptions (command: Command) { | |||
117 | .option('-d, --video-description <description>', 'Video description') | 119 | .option('-d, --video-description <description>', 'Video description') |
118 | .option('-P, --privacy <privacy_number>', 'Privacy') | 120 | .option('-P, --privacy <privacy_number>', 'Privacy') |
119 | .option('-C, --channel-name <channel_name>', 'Channel name') | 121 | .option('-C, --channel-name <channel_name>', 'Channel name') |
120 | .option('-m, --comments-enabled', 'Enable comments') | 122 | .option('--no-comments-enabled', 'Disable video comments') |
121 | .option('-s, --support <support>', 'Video support text') | 123 | .option('-s, --support <support>', 'Video support text') |
122 | .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video') | 124 | .option('--no-wait-transcoding', 'Do not wait transcoding before publishing the video') |
125 | .option('--no-download-enabled', 'Disable video download') | ||
123 | .option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info') | 126 | .option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info') |
124 | } | 127 | } |
125 | 128 | ||
126 | async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) { | 129 | async function buildVideoAttributesFromCommander (url: string, command: CommanderStatic, defaultAttributes: any = {}) { |
127 | const defaultBooleanAttributes = { | 130 | const defaultBooleanAttributes = { |
128 | nsfw: false, | 131 | nsfw: false, |
129 | commentsEnabled: true, | 132 | commentsEnabled: true, |
@@ -134,8 +137,8 @@ async function buildVideoAttributesFromCommander (url: string, command: Command, | |||
134 | const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {} | 137 | const booleanAttributes: { [id in keyof typeof defaultBooleanAttributes]: boolean } | {} = {} |
135 | 138 | ||
136 | for (const key of Object.keys(defaultBooleanAttributes)) { | 139 | for (const key of Object.keys(defaultBooleanAttributes)) { |
137 | if (command[ key ] !== undefined) { | 140 | if (command[key] !== undefined) { |
138 | booleanAttributes[key] = command[ key ] | 141 | booleanAttributes[key] = command[key] |
139 | } else if (defaultAttributes[key] !== undefined) { | 142 | } else if (defaultAttributes[key] !== undefined) { |
140 | booleanAttributes[key] = defaultAttributes[key] | 143 | booleanAttributes[key] = defaultAttributes[key] |
141 | } else { | 144 | } else { |
@@ -144,19 +147,19 @@ async function buildVideoAttributesFromCommander (url: string, command: Command, | |||
144 | } | 147 | } |
145 | 148 | ||
146 | const videoAttributes = { | 149 | const videoAttributes = { |
147 | name: command[ 'videoName' ] || defaultAttributes.name, | 150 | name: command['videoName'] || defaultAttributes.name, |
148 | category: command[ 'category' ] || defaultAttributes.category || undefined, | 151 | category: command['category'] || defaultAttributes.category || undefined, |
149 | licence: command[ 'licence' ] || defaultAttributes.licence || undefined, | 152 | licence: command['licence'] || defaultAttributes.licence || undefined, |
150 | language: command[ 'language' ] || defaultAttributes.language || undefined, | 153 | language: command['language'] || defaultAttributes.language || undefined, |
151 | privacy: command[ 'privacy' ] || defaultAttributes.privacy || VideoPrivacy.PUBLIC, | 154 | privacy: command['privacy'] || defaultAttributes.privacy || VideoPrivacy.PUBLIC, |
152 | support: command[ 'support' ] || defaultAttributes.support || undefined, | 155 | support: command['support'] || defaultAttributes.support || undefined, |
153 | description: command[ 'videoDescription' ] || defaultAttributes.description || undefined, | 156 | description: command['videoDescription'] || defaultAttributes.description || undefined, |
154 | tags: command[ 'tags' ] || defaultAttributes.tags || undefined | 157 | tags: command['tags'] || defaultAttributes.tags || undefined |
155 | } | 158 | } |
156 | 159 | ||
157 | Object.assign(videoAttributes, booleanAttributes) | 160 | Object.assign(videoAttributes, booleanAttributes) |
158 | 161 | ||
159 | if (command[ 'channelName' ]) { | 162 | if (command['channelName']) { |
160 | const res = await getVideoChannel(url, command['channelName']) | 163 | const res = await getVideoChannel(url, command['channelName']) |
161 | const videoChannel: VideoChannel = res.body | 164 | const videoChannel: VideoChannel = res.body |
162 | 165 | ||
@@ -172,9 +175,9 @@ async function buildVideoAttributesFromCommander (url: string, command: Command, | |||
172 | 175 | ||
173 | function getServerCredentials (program: any) { | 176 | function getServerCredentials (program: any) { |
174 | return Promise.all([ getSettings(), getNetrc() ]) | 177 | return Promise.all([ getSettings(), getNetrc() ]) |
175 | .then(([ settings, netrc ]) => { | 178 | .then(([ settings, netrc ]) => { |
176 | return getRemoteObjectOrDie(program, settings, netrc) | 179 | return getRemoteObjectOrDie(program, settings, netrc) |
177 | }) | 180 | }) |
178 | } | 181 | } |
179 | 182 | ||
180 | function getLogger (logLevel = 'info') { | 183 | function getLogger (logLevel = 'info') { |
@@ -211,7 +214,6 @@ function getLogger (logLevel = 'info') { | |||
211 | 214 | ||
212 | export { | 215 | export { |
213 | version, | 216 | version, |
214 | config, | ||
215 | getLogger, | 217 | getLogger, |
216 | getSettings, | 218 | getSettings, |
217 | getNetrc, | 219 | getNetrc, |
@@ -222,5 +224,7 @@ export { | |||
222 | getServerCredentials, | 224 | getServerCredentials, |
223 | 225 | ||
224 | buildCommonVideoOptions, | 226 | buildCommonVideoOptions, |
225 | buildVideoAttributesFromCommander | 227 | buildVideoAttributesFromCommander, |
228 | |||
229 | getAdminTokenOrDie | ||
226 | } | 230 | } |
diff --git a/server/tools/package.json b/server/tools/package.json index 40959d76e..3821850c0 100644 --- a/server/tools/package.json +++ b/server/tools/package.json | |||
@@ -3,12 +3,13 @@ | |||
3 | "version": "1.0.0", | 3 | "version": "1.0.0", |
4 | "private": true, | 4 | "private": true, |
5 | "dependencies": { | 5 | "dependencies": { |
6 | "application-config": "^1.0.1", | 6 | "application-config": "^2.0.0", |
7 | "cli-table": "^0.3.1", | 7 | "cli-table3": "^0.6.0", |
8 | "netrc-parser": "^3.1.6", | 8 | "netrc-parser": "^3.1.6", |
9 | "webtorrent-hybrid": "^4.0.1" | 9 | "webtorrent-hybrid": "^4.0.1" |
10 | }, | 10 | }, |
11 | "summon": { | 11 | "summon": { |
12 | "silent": true | 12 | "silent": true |
13 | } | 13 | }, |
14 | "devDependencies": {} | ||
14 | } | 15 | } |
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts index 6597a5c36..c1a804f83 100644 --- a/server/tools/peertube-auth.ts +++ b/server/tools/peertube-auth.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | // eslint-disable @typescript-eslint/no-unnecessary-type-assertion | ||
2 | |||
1 | import { registerTSPaths } from '../helpers/register-ts-paths' | 3 | import { registerTSPaths } from '../helpers/register-ts-paths' |
2 | registerTSPaths() | 4 | registerTSPaths() |
3 | 5 | ||
@@ -5,9 +7,8 @@ import * as program from 'commander' | |||
5 | import * as prompt from 'prompt' | 7 | import * as prompt from 'prompt' |
6 | import { getNetrc, getSettings, writeSettings } from './cli' | 8 | import { getNetrc, getSettings, writeSettings } from './cli' |
7 | import { isUserUsernameValid } from '../helpers/custom-validators/users' | 9 | import { isUserUsernameValid } from '../helpers/custom-validators/users' |
8 | import { getAccessToken, login } from '../../shared/extra-utils' | 10 | import { getAccessToken } from '../../shared/extra-utils' |
9 | 11 | import * as CliTable3 from 'cli-table3' | |
10 | const Table = require('cli-table') | ||
11 | 12 | ||
12 | async function delInstance (url: string) { | 13 | async function delInstance (url: string) { |
13 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) | 14 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) |
@@ -108,10 +109,10 @@ program | |||
108 | .action(async () => { | 109 | .action(async () => { |
109 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) | 110 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) |
110 | 111 | ||
111 | const table = new Table({ | 112 | const table = new CliTable3({ |
112 | head: ['instance', 'login'], | 113 | head: [ 'instance', 'login' ], |
113 | colWidths: [30, 30] | 114 | colWidths: [ 30, 30 ] |
114 | }) | 115 | }) as any |
115 | 116 | ||
116 | settings.remotes.forEach(element => { | 117 | settings.remotes.forEach(element => { |
117 | if (!netrc.machines[element]) return | 118 | if (!netrc.machines[element]) return |
@@ -132,7 +133,7 @@ program | |||
132 | .description('set an existing entry as default') | 133 | .description('set an existing entry as default') |
133 | .action(async url => { | 134 | .action(async url => { |
134 | const settings = await getSettings() | 135 | const settings = await getSettings() |
135 | const instanceExists = settings.remotes.indexOf(url) !== -1 | 136 | const instanceExists = settings.remotes.includes(url) |
136 | 137 | ||
137 | if (instanceExists) { | 138 | if (instanceExists) { |
138 | settings.default = settings.remotes.indexOf(url) | 139 | settings.default = settings.remotes.indexOf(url) |
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index eaa792763..2c9eabe98 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts | |||
@@ -1,10 +1,6 @@ | |||
1 | import { registerTSPaths } from '../helpers/register-ts-paths' | 1 | import { registerTSPaths } from '../helpers/register-ts-paths' |
2 | |||
3 | registerTSPaths() | 2 | registerTSPaths() |
4 | 3 | ||
5 | // FIXME: https://github.com/nodejs/node/pull/16853 | ||
6 | require('tls').DEFAULT_ECDH_CURVE = 'auto' | ||
7 | |||
8 | import * as program from 'commander' | 4 | import * as program from 'commander' |
9 | import { join } from 'path' | 5 | import { join } from 'path' |
10 | import { doRequestAndSaveToFile } from '../helpers/requests' | 6 | import { doRequestAndSaveToFile } from '../helpers/requests' |
@@ -16,7 +12,7 @@ import { accessSync, constants } from 'fs' | |||
16 | import { remove } from 'fs-extra' | 12 | import { remove } from 'fs-extra' |
17 | import { sha256 } from '../helpers/core-utils' | 13 | import { sha256 } from '../helpers/core-utils' |
18 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' | 14 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' |
19 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials, getLogger } from './cli' | 15 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getLogger, getServerCredentials } from './cli' |
20 | 16 | ||
21 | type UserInfo = { | 17 | type UserInfo = { |
22 | username: string | 18 | username: string |
@@ -42,32 +38,32 @@ command | |||
42 | .option('--first <first>', 'Process first n elements of returned playlist') | 38 | .option('--first <first>', 'Process first n elements of returned playlist') |
43 | .option('--last <last>', 'Process last n elements of returned playlist') | 39 | .option('--last <last>', 'Process last n elements of returned playlist') |
44 | .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname) | 40 | .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname) |
41 | .usage("[global options] [ -- youtube-dl options]") | ||
45 | .parse(process.argv) | 42 | .parse(process.argv) |
46 | 43 | ||
47 | let log = getLogger(program[ 'verbose' ]) | 44 | const log = getLogger(program['verbose']) |
48 | 45 | ||
49 | getServerCredentials(command) | 46 | getServerCredentials(command) |
50 | .then(({ url, username, password }) => { | 47 | .then(({ url, username, password }) => { |
51 | if (!program[ 'targetUrl' ]) { | 48 | if (!program['targetUrl']) { |
52 | exitError('--target-url field is required.') | 49 | exitError('--target-url field is required.') |
53 | } | 50 | } |
54 | 51 | ||
55 | try { | 52 | try { |
56 | accessSync(program[ 'tmpdir' ], constants.R_OK | constants.W_OK) | 53 | accessSync(program['tmpdir'], constants.R_OK | constants.W_OK) |
57 | } catch (e) { | 54 | } catch (e) { |
58 | exitError('--tmpdir %s: directory does not exist or is not accessible', program[ 'tmpdir' ]) | 55 | exitError('--tmpdir %s: directory does not exist or is not accessible', program['tmpdir']) |
59 | } | 56 | } |
60 | 57 | ||
61 | url = normalizeTargetUrl(url) | 58 | url = normalizeTargetUrl(url) |
62 | program[ 'targetUrl' ] = normalizeTargetUrl(program[ 'targetUrl' ]) | 59 | program['targetUrl'] = normalizeTargetUrl(program['targetUrl']) |
63 | 60 | ||
64 | const user = { username, password } | 61 | const user = { username, password } |
65 | 62 | ||
66 | run(url, user) | 63 | run(url, user) |
67 | .catch(err => { | 64 | .catch(err => exitError(err)) |
68 | exitError(err) | ||
69 | }) | ||
70 | }) | 65 | }) |
66 | .catch(err => console.error(err)) | ||
71 | 67 | ||
72 | async function run (url: string, user: UserInfo) { | 68 | async function run (url: string, user: UserInfo) { |
73 | if (!user.password) { | 69 | if (!user.password) { |
@@ -76,43 +72,48 @@ async function run (url: string, user: UserInfo) { | |||
76 | 72 | ||
77 | const youtubeDL = await safeGetYoutubeDL() | 73 | const youtubeDL = await safeGetYoutubeDL() |
78 | 74 | ||
79 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] | 75 | const options = [ '-j', '--flat-playlist', '--playlist-reverse', ...command.args ] |
80 | youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { | 76 | |
77 | youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => { | ||
81 | if (err) { | 78 | if (err) { |
82 | exitError(err.message) | 79 | exitError(err.stderr + ' ' + err.message) |
83 | } | 80 | } |
84 | 81 | ||
85 | let infoArray: any[] | 82 | let infoArray: any[] |
86 | 83 | ||
87 | // Normalize utf8 fields | 84 | // Normalize utf8 fields |
88 | infoArray = [].concat(info) | 85 | infoArray = [].concat(info) |
89 | if (program[ 'first' ]) { | 86 | if (program['first']) { |
90 | infoArray = infoArray.slice(0, program[ 'first' ]) | 87 | infoArray = infoArray.slice(0, program['first']) |
91 | } else if (program[ 'last' ]) { | 88 | } else if (program['last']) { |
92 | infoArray = infoArray.slice(-program[ 'last' ]) | 89 | infoArray = infoArray.slice(-program['last']) |
93 | } | 90 | } |
94 | infoArray = infoArray.map(i => normalizeObject(i)) | 91 | infoArray = infoArray.map(i => normalizeObject(i)) |
95 | 92 | ||
96 | log.info('Will download and upload %d videos.\n', infoArray.length) | 93 | log.info('Will download and upload %d videos.\n', infoArray.length) |
97 | 94 | ||
98 | for (const info of infoArray) { | 95 | for (const info of infoArray) { |
99 | await processVideo({ | 96 | try { |
100 | cwd: program[ 'tmpdir' ], | 97 | await processVideo({ |
101 | url, | 98 | cwd: program['tmpdir'], |
102 | user, | 99 | url, |
103 | youtubeInfo: info | 100 | user, |
104 | }) | 101 | youtubeInfo: info |
102 | }) | ||
103 | } catch (err) { | ||
104 | console.error('Cannot process video.', { info, url }) | ||
105 | } | ||
105 | } | 106 | } |
106 | 107 | ||
107 | log.info('Video/s for user %s imported: %s', user.username, program[ 'targetUrl' ]) | 108 | log.info('Video/s for user %s imported: %s', user.username, program['targetUrl']) |
108 | process.exit(0) | 109 | process.exit(0) |
109 | }) | 110 | }) |
110 | } | 111 | } |
111 | 112 | ||
112 | function processVideo (parameters: { | 113 | function processVideo (parameters: { |
113 | cwd: string, | 114 | cwd: string |
114 | url: string, | 115 | url: string |
115 | user: { username: string, password: string }, | 116 | user: { username: string, password: string } |
116 | youtubeInfo: any | 117 | youtubeInfo: any |
117 | }) { | 118 | }) { |
118 | const { youtubeInfo, cwd, url, user } = parameters | 119 | const { youtubeInfo, cwd, url, user } = parameters |
@@ -123,17 +124,17 @@ function processVideo (parameters: { | |||
123 | const videoInfo = await fetchObject(youtubeInfo) | 124 | const videoInfo = await fetchObject(youtubeInfo) |
124 | log.debug('Fetched object.', videoInfo) | 125 | log.debug('Fetched object.', videoInfo) |
125 | 126 | ||
126 | if (program[ 'since' ]) { | 127 | if (program['since']) { |
127 | if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) { | 128 | if (buildOriginallyPublishedAt(videoInfo).getTime() < program['since'].getTime()) { |
128 | log.info('Video "%s" has been published before "%s", don\'t upload it.\n', | 129 | log.info('Video "%s" has been published before "%s", don\'t upload it.\n', |
129 | videoInfo.title, formatDate(program[ 'since' ])) | 130 | videoInfo.title, formatDate(program['since'])) |
130 | return res() | 131 | return res() |
131 | } | 132 | } |
132 | } | 133 | } |
133 | if (program[ 'until' ]) { | 134 | if (program['until']) { |
134 | if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) { | 135 | if (buildOriginallyPublishedAt(videoInfo).getTime() > program['until'].getTime()) { |
135 | log.info('Video "%s" has been published after "%s", don\'t upload it.\n', | 136 | log.info('Video "%s" has been published after "%s", don\'t upload it.\n', |
136 | videoInfo.title, formatDate(program[ 'until' ])) | 137 | videoInfo.title, formatDate(program['until'])) |
137 | return res() | 138 | return res() |
138 | } | 139 | } |
139 | } | 140 | } |
@@ -151,7 +152,7 @@ function processVideo (parameters: { | |||
151 | 152 | ||
152 | log.info('Downloading video "%s"...', videoInfo.title) | 153 | log.info('Downloading video "%s"...', videoInfo.title) |
153 | 154 | ||
154 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] | 155 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', ...command.args, '-o', path ] |
155 | try { | 156 | try { |
156 | const youtubeDL = await safeGetYoutubeDL() | 157 | const youtubeDL = await safeGetYoutubeDL() |
157 | youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { | 158 | youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { |
@@ -178,11 +179,11 @@ function processVideo (parameters: { | |||
178 | } | 179 | } |
179 | 180 | ||
180 | async function uploadVideoOnPeerTube (parameters: { | 181 | async function uploadVideoOnPeerTube (parameters: { |
181 | videoInfo: any, | 182 | videoInfo: any |
182 | videoPath: string, | 183 | videoPath: string |
183 | cwd: string, | 184 | cwd: string |
184 | url: string, | 185 | url: string |
185 | user: { username: string; password: string } | 186 | user: { username: string, password: string } |
186 | }) { | 187 | }) { |
187 | const { videoInfo, videoPath, cwd, url, user } = parameters | 188 | const { videoInfo, videoPath, cwd, url, user } = parameters |
188 | 189 | ||
@@ -210,9 +211,9 @@ async function uploadVideoOnPeerTube (parameters: { | |||
210 | 211 | ||
211 | const defaultAttributes = { | 212 | const defaultAttributes = { |
212 | name: truncate(videoInfo.title, { | 213 | name: truncate(videoInfo.title, { |
213 | 'length': CONSTRAINTS_FIELDS.VIDEOS.NAME.max, | 214 | length: CONSTRAINTS_FIELDS.VIDEOS.NAME.max, |
214 | 'separator': /,? +/, | 215 | separator: /,? +/, |
215 | 'omission': ' […]' | 216 | omission: ' […]' |
216 | }), | 217 | }), |
217 | category, | 218 | category, |
218 | licence, | 219 | licence, |
@@ -259,7 +260,7 @@ async function uploadVideoOnPeerTube (parameters: { | |||
259 | async function getCategory (categories: string[], url: string) { | 260 | async function getCategory (categories: string[], url: string) { |
260 | if (!categories) return undefined | 261 | if (!categories) return undefined |
261 | 262 | ||
262 | const categoryString = categories[ 0 ] | 263 | const categoryString = categories[0] |
263 | 264 | ||
264 | if (categoryString === 'News & Politics') return 11 | 265 | if (categoryString === 'News & Politics') return 11 |
265 | 266 | ||
@@ -267,7 +268,7 @@ async function getCategory (categories: string[], url: string) { | |||
267 | const categoriesServer = res.body | 268 | const categoriesServer = res.body |
268 | 269 | ||
269 | for (const key of Object.keys(categoriesServer)) { | 270 | for (const key of Object.keys(categoriesServer)) { |
270 | const categoryServer = categoriesServer[ key ] | 271 | const categoryServer = categoriesServer[key] |
271 | if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10) | 272 | if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10) |
272 | } | 273 | } |
273 | 274 | ||
@@ -277,7 +278,7 @@ async function getCategory (categories: string[], url: string) { | |||
277 | function getLicence (licence: string) { | 278 | function getLicence (licence: string) { |
278 | if (!licence) return undefined | 279 | if (!licence) return undefined |
279 | 280 | ||
280 | if (licence.indexOf('Creative Commons Attribution licence') !== -1) return 1 | 281 | if (licence.includes('Creative Commons Attribution licence')) return 1 |
281 | 282 | ||
282 | return undefined | 283 | return undefined |
283 | } | 284 | } |
@@ -289,12 +290,12 @@ function normalizeObject (obj: any) { | |||
289 | // Deprecated key | 290 | // Deprecated key |
290 | if (key === 'resolution') continue | 291 | if (key === 'resolution') continue |
291 | 292 | ||
292 | const value = obj[ key ] | 293 | const value = obj[key] |
293 | 294 | ||
294 | if (typeof value === 'string') { | 295 | if (typeof value === 'string') { |
295 | newObj[ key ] = value.normalize() | 296 | newObj[key] = value.normalize() |
296 | } else { | 297 | } else { |
297 | newObj[ key ] = value | 298 | newObj[key] = value |
298 | } | 299 | } |
299 | } | 300 | } |
300 | 301 | ||
@@ -306,7 +307,7 @@ function fetchObject (info: any) { | |||
306 | 307 | ||
307 | return new Promise<any>(async (res, rej) => { | 308 | return new Promise<any>(async (res, rej) => { |
308 | const youtubeDL = await safeGetYoutubeDL() | 309 | const youtubeDL = await safeGetYoutubeDL() |
309 | youtubeDL.getInfo(url, undefined, processOptions, async (err, videoInfo) => { | 310 | youtubeDL.getInfo(url, undefined, processOptions, (err, videoInfo) => { |
310 | if (err) return rej(err) | 311 | if (err) return rej(err) |
311 | 312 | ||
312 | const videoInfoWithUrl = Object.assign(videoInfo, { url }) | 313 | const videoInfoWithUrl = Object.assign(videoInfo, { url }) |
@@ -317,10 +318,10 @@ function fetchObject (info: any) { | |||
317 | 318 | ||
318 | function buildUrl (info: any) { | 319 | function buildUrl (info: any) { |
319 | const webpageUrl = info.webpage_url as string | 320 | const webpageUrl = info.webpage_url as string |
320 | if (webpageUrl && webpageUrl.match(/^https?:\/\//)) return webpageUrl | 321 | if (webpageUrl?.match(/^https?:\/\//)) return webpageUrl |
321 | 322 | ||
322 | const url = info.url as string | 323 | const url = info.url as string |
323 | if (url && url.match(/^https?:\/\//)) return url | 324 | if (url?.match(/^https?:\/\//)) return url |
324 | 325 | ||
325 | // It seems youtube-dl does not return the video url | 326 | // It seems youtube-dl does not return the video url |
326 | return 'https://www.youtube.com/watch?v=' + info.id | 327 | return 'https://www.youtube.com/watch?v=' + info.id |
@@ -388,7 +389,7 @@ function parseDate (dateAsStr: string): Date { | |||
388 | } | 389 | } |
389 | 390 | ||
390 | function formatDate (date: Date): string { | 391 | function formatDate (date: Date): string { |
391 | return date.toISOString().split('T')[ 0 ] | 392 | return date.toISOString().split('T')[0] |
392 | } | 393 | } |
393 | 394 | ||
394 | function exitError (message: string, ...meta: any[]) { | 395 | function exitError (message: string, ...meta: any[]) { |
diff --git a/server/tools/peertube-plugins.ts b/server/tools/peertube-plugins.ts index e40606107..05b75fab2 100644 --- a/server/tools/peertube-plugins.ts +++ b/server/tools/peertube-plugins.ts | |||
@@ -1,17 +1,15 @@ | |||
1 | // eslint-disable @typescript-eslint/no-unnecessary-type-assertion | ||
2 | |||
1 | import { registerTSPaths } from '../helpers/register-ts-paths' | 3 | import { registerTSPaths } from '../helpers/register-ts-paths' |
2 | registerTSPaths() | 4 | registerTSPaths() |
3 | 5 | ||
4 | import * as program from 'commander' | 6 | import * as program from 'commander' |
5 | import { PluginType } from '../../shared/models/plugins/plugin.type' | 7 | import { PluginType } from '../../shared/models/plugins/plugin.type' |
6 | import { getAccessToken } from '../../shared/extra-utils/users/login' | ||
7 | import { getMyUserInformation } from '../../shared/extra-utils/users/users' | ||
8 | import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' | 8 | import { installPlugin, listPlugins, uninstallPlugin, updatePlugin } from '../../shared/extra-utils/server/plugins' |
9 | import { getServerCredentials } from './cli' | 9 | import { getAdminTokenOrDie, getServerCredentials } from './cli' |
10 | import { User, UserRole } from '../../shared/models/users' | ||
11 | import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' | 10 | import { PeerTubePlugin } from '../../shared/models/plugins/peertube-plugin.model' |
12 | import { isAbsolute } from 'path' | 11 | import { isAbsolute } from 'path' |
13 | 12 | import * as CliTable3 from 'cli-table3' | |
14 | const Table = require('cli-table') | ||
15 | 13 | ||
16 | program | 14 | program |
17 | .name('plugins') | 15 | .name('plugins') |
@@ -82,10 +80,10 @@ async function pluginsListCLI () { | |||
82 | }) | 80 | }) |
83 | const plugins: PeerTubePlugin[] = res.body.data | 81 | const plugins: PeerTubePlugin[] = res.body.data |
84 | 82 | ||
85 | const table = new Table({ | 83 | const table = new CliTable3({ |
86 | head: ['name', 'version', 'homepage'], | 84 | head: [ 'name', 'version', 'homepage' ], |
87 | colWidths: [ 50, 10, 50 ] | 85 | colWidths: [ 50, 10, 50 ] |
88 | }) | 86 | }) as any |
89 | 87 | ||
90 | for (const plugin of plugins) { | 88 | for (const plugin of plugins) { |
91 | const npmName = plugin.type === PluginType.PLUGIN | 89 | const npmName = plugin.type === PluginType.PLUGIN |
@@ -128,7 +126,6 @@ async function installPluginCLI (options: any) { | |||
128 | } catch (err) { | 126 | } catch (err) { |
129 | console.error('Cannot install plugin.', err) | 127 | console.error('Cannot install plugin.', err) |
130 | process.exit(-1) | 128 | process.exit(-1) |
131 | return | ||
132 | } | 129 | } |
133 | 130 | ||
134 | console.log('Plugin installed.') | 131 | console.log('Plugin installed.') |
@@ -160,7 +157,6 @@ async function updatePluginCLI (options: any) { | |||
160 | } catch (err) { | 157 | } catch (err) { |
161 | console.error('Cannot update plugin.', err) | 158 | console.error('Cannot update plugin.', err) |
162 | process.exit(-1) | 159 | process.exit(-1) |
163 | return | ||
164 | } | 160 | } |
165 | 161 | ||
166 | console.log('Plugin updated.') | 162 | console.log('Plugin updated.') |
@@ -181,27 +177,13 @@ async function uninstallPluginCLI (options: any) { | |||
181 | await uninstallPlugin({ | 177 | await uninstallPlugin({ |
182 | url, | 178 | url, |
183 | accessToken, | 179 | accessToken, |
184 | npmName: options[ 'npmName' ] | 180 | npmName: options['npmName'] |
185 | }) | 181 | }) |
186 | } catch (err) { | 182 | } catch (err) { |
187 | console.error('Cannot uninstall plugin.', err) | 183 | console.error('Cannot uninstall plugin.', err) |
188 | process.exit(-1) | 184 | process.exit(-1) |
189 | return | ||
190 | } | 185 | } |
191 | 186 | ||
192 | console.log('Plugin uninstalled.') | 187 | console.log('Plugin uninstalled.') |
193 | process.exit(0) | 188 | process.exit(0) |
194 | } | 189 | } |
195 | |||
196 | async function getAdminTokenOrDie (url: string, username: string, password: string) { | ||
197 | const accessToken = await getAccessToken(url, username, password) | ||
198 | const resMe = await getMyUserInformation(url, accessToken) | ||
199 | const me: User = resMe.body | ||
200 | |||
201 | if (me.role !== UserRole.ADMINISTRATOR) { | ||
202 | console.error('Cannot list plugins if you are not administrator.') | ||
203 | process.exit(-1) | ||
204 | } | ||
205 | |||
206 | return accessToken | ||
207 | } | ||
diff --git a/server/tools/peertube-redundancy.ts b/server/tools/peertube-redundancy.ts new file mode 100644 index 000000000..1ab58a438 --- /dev/null +++ b/server/tools/peertube-redundancy.ts | |||
@@ -0,0 +1,197 @@ | |||
1 | // eslint-disable @typescript-eslint/no-unnecessary-type-assertion | ||
2 | |||
3 | import { registerTSPaths } from '../helpers/register-ts-paths' | ||
4 | registerTSPaths() | ||
5 | |||
6 | import * as program from 'commander' | ||
7 | import { getAdminTokenOrDie, getServerCredentials } from './cli' | ||
8 | import { VideoRedundanciesTarget, VideoRedundancy } from '@shared/models' | ||
9 | import { addVideoRedundancy, listVideoRedundancies, removeVideoRedundancy } from '@shared/extra-utils/server/redundancy' | ||
10 | import validator from 'validator' | ||
11 | import * as CliTable3 from 'cli-table3' | ||
12 | import { URL } from 'url' | ||
13 | import { uniq } from 'lodash' | ||
14 | |||
15 | import bytes = require('bytes') | ||
16 | |||
17 | program | ||
18 | .name('plugins') | ||
19 | .usage('[command] [options]') | ||
20 | |||
21 | program | ||
22 | .command('list-remote-redundancies') | ||
23 | .description('List remote redundancies on your videos') | ||
24 | .option('-u, --url <url>', 'Server url') | ||
25 | .option('-U, --username <username>', 'Username') | ||
26 | .option('-p, --password <token>', 'Password') | ||
27 | .action(() => listRedundanciesCLI('my-videos')) | ||
28 | |||
29 | program | ||
30 | .command('list-my-redundancies') | ||
31 | .description('List your redundancies of remote videos') | ||
32 | .option('-u, --url <url>', 'Server url') | ||
33 | .option('-U, --username <username>', 'Username') | ||
34 | .option('-p, --password <token>', 'Password') | ||
35 | .action(() => listRedundanciesCLI('remote-videos')) | ||
36 | |||
37 | program | ||
38 | .command('add') | ||
39 | .description('Duplicate a video in your redundancy system') | ||
40 | .option('-u, --url <url>', 'Server url') | ||
41 | .option('-U, --username <username>', 'Username') | ||
42 | .option('-p, --password <token>', 'Password') | ||
43 | .option('-v, --video <videoId>', 'Video id to duplicate') | ||
44 | .action((options) => addRedundancyCLI(options)) | ||
45 | |||
46 | program | ||
47 | .command('remove') | ||
48 | .description('Remove a video from your redundancies') | ||
49 | .option('-u, --url <url>', 'Server url') | ||
50 | .option('-U, --username <username>', 'Username') | ||
51 | .option('-p, --password <token>', 'Password') | ||
52 | .option('-v, --video <videoId>', 'Video id to remove from redundancies') | ||
53 | .action((options) => removeRedundancyCLI(options)) | ||
54 | |||
55 | if (!process.argv.slice(2).length) { | ||
56 | program.outputHelp() | ||
57 | } | ||
58 | |||
59 | program.parse(process.argv) | ||
60 | |||
61 | // ---------------------------------------------------------------------------- | ||
62 | |||
63 | async function listRedundanciesCLI (target: VideoRedundanciesTarget) { | ||
64 | const { url, username, password } = await getServerCredentials(program) | ||
65 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
66 | |||
67 | const redundancies = await listVideoRedundanciesData(url, accessToken, target) | ||
68 | |||
69 | const table = new CliTable3({ | ||
70 | head: [ 'video id', 'video name', 'video url', 'files', 'playlists', 'by instances', 'total size' ] | ||
71 | }) as any | ||
72 | |||
73 | for (const redundancy of redundancies) { | ||
74 | const webtorrentFiles = redundancy.redundancies.files | ||
75 | const streamingPlaylists = redundancy.redundancies.streamingPlaylists | ||
76 | |||
77 | let totalSize = '' | ||
78 | if (target === 'remote-videos') { | ||
79 | const tmp = webtorrentFiles.concat(streamingPlaylists) | ||
80 | .reduce((a, b) => a + b.size, 0) | ||
81 | |||
82 | totalSize = bytes(tmp) | ||
83 | } | ||
84 | |||
85 | const instances = uniq( | ||
86 | webtorrentFiles.concat(streamingPlaylists) | ||
87 | .map(r => r.fileUrl) | ||
88 | .map(u => new URL(u).host) | ||
89 | ) | ||
90 | |||
91 | table.push([ | ||
92 | redundancy.id.toString(), | ||
93 | redundancy.name, | ||
94 | redundancy.url, | ||
95 | webtorrentFiles.length, | ||
96 | streamingPlaylists.length, | ||
97 | instances.join('\n'), | ||
98 | totalSize | ||
99 | ]) | ||
100 | } | ||
101 | |||
102 | console.log(table.toString()) | ||
103 | process.exit(0) | ||
104 | } | ||
105 | |||
106 | async function addRedundancyCLI (options: { videoId: number }) { | ||
107 | const { url, username, password } = await getServerCredentials(program) | ||
108 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
109 | |||
110 | if (!options['video'] || validator.isInt('' + options['video']) === false) { | ||
111 | console.error('You need to specify the video id to duplicate and it should be a number.\n') | ||
112 | program.outputHelp() | ||
113 | process.exit(-1) | ||
114 | } | ||
115 | |||
116 | try { | ||
117 | await addVideoRedundancy({ | ||
118 | url, | ||
119 | accessToken, | ||
120 | videoId: options['video'] | ||
121 | }) | ||
122 | |||
123 | console.log('Video will be duplicated by your instance!') | ||
124 | |||
125 | process.exit(0) | ||
126 | } catch (err) { | ||
127 | if (err.message.includes(409)) { | ||
128 | console.error('This video is already duplicated by your instance.') | ||
129 | } else if (err.message.includes(404)) { | ||
130 | console.error('This video id does not exist.') | ||
131 | } else { | ||
132 | console.error(err) | ||
133 | } | ||
134 | |||
135 | process.exit(-1) | ||
136 | } | ||
137 | } | ||
138 | |||
139 | async function removeRedundancyCLI (options: { videoId: number }) { | ||
140 | const { url, username, password } = await getServerCredentials(program) | ||
141 | const accessToken = await getAdminTokenOrDie(url, username, password) | ||
142 | |||
143 | if (!options['video'] || validator.isInt('' + options['video']) === false) { | ||
144 | console.error('You need to specify the video id to remove from your redundancies.\n') | ||
145 | program.outputHelp() | ||
146 | process.exit(-1) | ||
147 | } | ||
148 | |||
149 | const videoId = parseInt(options['video'] + '', 10) | ||
150 | |||
151 | let redundancies = await listVideoRedundanciesData(url, accessToken, 'my-videos') | ||
152 | let videoRedundancy = redundancies.find(r => videoId === r.id) | ||
153 | |||
154 | if (!videoRedundancy) { | ||
155 | redundancies = await listVideoRedundanciesData(url, accessToken, 'remote-videos') | ||
156 | videoRedundancy = redundancies.find(r => videoId === r.id) | ||
157 | } | ||
158 | |||
159 | if (!videoRedundancy) { | ||
160 | console.error('Video redundancy not found.') | ||
161 | process.exit(-1) | ||
162 | } | ||
163 | |||
164 | try { | ||
165 | const ids = videoRedundancy.redundancies.files | ||
166 | .concat(videoRedundancy.redundancies.streamingPlaylists) | ||
167 | .map(r => r.id) | ||
168 | |||
169 | for (const id of ids) { | ||
170 | await removeVideoRedundancy({ | ||
171 | url, | ||
172 | accessToken, | ||
173 | redundancyId: id | ||
174 | }) | ||
175 | } | ||
176 | |||
177 | console.log('Video redundancy removed!') | ||
178 | |||
179 | process.exit(0) | ||
180 | } catch (err) { | ||
181 | console.error(err) | ||
182 | process.exit(-1) | ||
183 | } | ||
184 | } | ||
185 | |||
186 | async function listVideoRedundanciesData (url: string, accessToken: string, target: VideoRedundanciesTarget) { | ||
187 | const res = await listVideoRedundancies({ | ||
188 | url, | ||
189 | accessToken, | ||
190 | start: 0, | ||
191 | count: 100, | ||
192 | sort: 'name', | ||
193 | target | ||
194 | }) | ||
195 | |||
196 | return res.body.data as VideoRedundancy[] | ||
197 | } | ||
diff --git a/server/tools/peertube-repl.ts b/server/tools/peertube-repl.ts index ab6e215d9..a5c35e9ea 100644 --- a/server/tools/peertube-repl.ts +++ b/server/tools/peertube-repl.ts | |||
@@ -4,14 +4,10 @@ registerTSPaths() | |||
4 | import * as repl from 'repl' | 4 | import * as repl from 'repl' |
5 | import * as path from 'path' | 5 | import * as path from 'path' |
6 | import * as _ from 'lodash' | 6 | import * as _ from 'lodash' |
7 | import * as uuidv1 from 'uuid/v1' | 7 | import { uuidv1, uuidv3, uuidv4, uuidv5 } from 'uuid' |
8 | import * as uuidv3 from 'uuid/v3' | ||
9 | import * as uuidv4 from 'uuid/v4' | ||
10 | import * as uuidv5 from 'uuid/v5' | ||
11 | import * as Sequelize from 'sequelize' | 8 | import * as Sequelize from 'sequelize' |
12 | import * as YoutubeDL from 'youtube-dl' | 9 | import * as YoutubeDL from 'youtube-dl' |
13 | 10 | import { initDatabaseModels, sequelizeTypescript } from '../initializers/database' | |
14 | import { initDatabaseModels, sequelizeTypescript } from '../initializers' | ||
15 | import * as cli from '../tools/cli' | 11 | import * as cli from '../tools/cli' |
16 | import { logger } from '../helpers/logger' | 12 | import { logger } from '../helpers/logger' |
17 | import * as constants from '../initializers/constants' | 13 | import * as constants from '../initializers/constants' |
@@ -31,22 +27,39 @@ const start = async () => { | |||
31 | const initContext = (replServer) => { | 27 | const initContext = (replServer) => { |
32 | return (context) => { | 28 | return (context) => { |
33 | const properties = { | 29 | const properties = { |
34 | context, repl: replServer, env: process.env, | 30 | context, |
35 | lodash: _, path, | 31 | repl: replServer, |
36 | uuidv1, uuidv3, uuidv4, uuidv5, | 32 | env: process.env, |
37 | cli, logger, constants, | 33 | lodash: _, |
38 | Sequelize, sequelizeTypescript, modelsUtils, | 34 | path, |
39 | models: sequelizeTypescript.models, transaction: sequelizeTypescript.transaction, | 35 | uuidv1, |
40 | query: sequelizeTypescript.query, queryInterface: sequelizeTypescript.getQueryInterface(), | 36 | uuidv3, |
37 | uuidv4, | ||
38 | uuidv5, | ||
39 | cli, | ||
40 | logger, | ||
41 | constants, | ||
42 | Sequelize, | ||
43 | sequelizeTypescript, | ||
44 | modelsUtils, | ||
45 | models: sequelizeTypescript.models, | ||
46 | transaction: sequelizeTypescript.transaction, | ||
47 | query: sequelizeTypescript.query, | ||
48 | queryInterface: sequelizeTypescript.getQueryInterface(), | ||
41 | YoutubeDL, | 49 | YoutubeDL, |
42 | coreUtils, ffmpegUtils, peertubeCryptoUtils, signupUtils, utils, YoutubeDLUtils | 50 | coreUtils, |
51 | ffmpegUtils, | ||
52 | peertubeCryptoUtils, | ||
53 | signupUtils, | ||
54 | utils, | ||
55 | YoutubeDLUtils | ||
43 | } | 56 | } |
44 | 57 | ||
45 | for (let prop in properties) { | 58 | for (const prop in properties) { |
46 | Object.defineProperty(context, prop, { | 59 | Object.defineProperty(context, prop, { |
47 | configurable: false, | 60 | configurable: false, |
48 | enumerable: true, | 61 | enumerable: true, |
49 | value: properties[ prop ] | 62 | value: properties[prop] |
50 | }) | 63 | }) |
51 | } | 64 | } |
52 | } | 65 | } |
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts index f604c9bee..8de952e7b 100644 --- a/server/tools/peertube-upload.ts +++ b/server/tools/peertube-upload.ts | |||
@@ -24,14 +24,14 @@ command | |||
24 | 24 | ||
25 | getServerCredentials(command) | 25 | getServerCredentials(command) |
26 | .then(({ url, username, password }) => { | 26 | .then(({ url, username, password }) => { |
27 | if (!program[ 'videoName' ] || !program[ 'file' ]) { | 27 | if (!program['videoName'] || !program['file']) { |
28 | if (!program[ 'videoName' ]) console.error('--video-name is required.') | 28 | if (!program['videoName']) console.error('--video-name is required.') |
29 | if (!program[ 'file' ]) console.error('--file is required.') | 29 | if (!program['file']) console.error('--file is required.') |
30 | 30 | ||
31 | process.exit(-1) | 31 | process.exit(-1) |
32 | } | 32 | } |
33 | 33 | ||
34 | if (isAbsolute(program[ 'file' ]) === false) { | 34 | if (isAbsolute(program['file']) === false) { |
35 | console.error('File path should be absolute.') | 35 | console.error('File path should be absolute.') |
36 | process.exit(-1) | 36 | process.exit(-1) |
37 | } | 37 | } |
@@ -41,25 +41,26 @@ getServerCredentials(command) | |||
41 | process.exit(-1) | 41 | process.exit(-1) |
42 | }) | 42 | }) |
43 | }) | 43 | }) |
44 | .catch(err => console.error(err)) | ||
44 | 45 | ||
45 | async function run (url: string, username: string, password: string) { | 46 | async function run (url: string, username: string, password: string) { |
46 | const accessToken = await getAccessToken(url, username, password) | 47 | const accessToken = await getAccessToken(url, username, password) |
47 | 48 | ||
48 | await access(program[ 'file' ], constants.F_OK) | 49 | await access(program['file'], constants.F_OK) |
49 | 50 | ||
50 | console.log('Uploading %s video...', program[ 'videoName' ]) | 51 | console.log('Uploading %s video...', program['videoName']) |
51 | 52 | ||
52 | const videoAttributes = await buildVideoAttributesFromCommander(url, program) | 53 | const videoAttributes = await buildVideoAttributesFromCommander(url, program) |
53 | 54 | ||
54 | Object.assign(videoAttributes, { | 55 | Object.assign(videoAttributes, { |
55 | fixture: program[ 'file' ], | 56 | fixture: program['file'], |
56 | thumbnailfile: program[ 'thumbnail' ], | 57 | thumbnailfile: program['thumbnail'], |
57 | previewfile: program[ 'preview' ] | 58 | previewfile: program['preview'] |
58 | }) | 59 | }) |
59 | 60 | ||
60 | try { | 61 | try { |
61 | await uploadVideo(url, accessToken, videoAttributes) | 62 | await uploadVideo(url, accessToken, videoAttributes) |
62 | console.log(`Video ${program[ 'videoName' ]} uploaded.`) | 63 | console.log(`Video ${program['videoName']} uploaded.`) |
63 | process.exit(0) | 64 | process.exit(0) |
64 | } catch (err) { | 65 | } catch (err) { |
65 | console.error(require('util').inspect(err)) | 66 | console.error(require('util').inspect(err)) |
diff --git a/server/tools/peertube-watch.ts b/server/tools/peertube-watch.ts index 9ac1d05f9..b8e750a37 100644 --- a/server/tools/peertube-watch.ts +++ b/server/tools/peertube-watch.ts | |||
@@ -29,16 +29,10 @@ program | |||
29 | console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') | 29 | console.log(' $ peertube watch https://peertube.cpy.re/videos/watch/e8a1af4e-414a-4d58-bfe6-2146eed06d10') |
30 | console.log() | 30 | console.log() |
31 | }) | 31 | }) |
32 | .action((url, cmd) => { | 32 | .action((url, cmd) => run(url, cmd)) |
33 | run(url, cmd) | ||
34 | .catch(err => { | ||
35 | console.error(err) | ||
36 | process.exit(-1) | ||
37 | }) | ||
38 | }) | ||
39 | .parse(process.argv) | 33 | .parse(process.argv) |
40 | 34 | ||
41 | async function run (url: string, program: any) { | 35 | function run (url: string, program: any) { |
42 | if (!url) { | 36 | if (!url) { |
43 | console.error('<url> positional argument is required.') | 37 | console.error('<url> positional argument is required.') |
44 | process.exit(-1) | 38 | process.exit(-1) |
@@ -49,5 +43,10 @@ async function run (url: string, program: any) { | |||
49 | url.replace('videos/watch', 'download/torrents') + | 43 | url.replace('videos/watch', 'download/torrents') + |
50 | `-${program.resolution}.torrent` | 44 | `-${program.resolution}.torrent` |
51 | 45 | ||
52 | execSync(cmd + args) | 46 | try { |
47 | execSync(cmd + args) | ||
48 | } catch (err) { | ||
49 | console.error('Cannto exec command.', err) | ||
50 | process.exit(-1) | ||
51 | } | ||
53 | } | 52 | } |
diff --git a/server/tools/peertube.ts b/server/tools/peertube.ts index fc85c4210..88dd5f7f6 100644 --- a/server/tools/peertube.ts +++ b/server/tools/peertube.ts | |||
@@ -1,13 +1,12 @@ | |||
1 | #!/usr/bin/env node | 1 | #!/usr/bin/env node |
2 | 2 | ||
3 | /* eslint-disable no-useless-escape */ | ||
4 | |||
3 | import { registerTSPaths } from '../helpers/register-ts-paths' | 5 | import { registerTSPaths } from '../helpers/register-ts-paths' |
4 | registerTSPaths() | 6 | registerTSPaths() |
5 | 7 | ||
6 | import * as program from 'commander' | 8 | import * as program from 'commander' |
7 | import { | 9 | import { getSettings, version } from './cli' |
8 | version, | ||
9 | getSettings | ||
10 | } from './cli' | ||
11 | 10 | ||
12 | program | 11 | program |
13 | .version(version, '-v, --version') | 12 | .version(version, '-v, --version') |
@@ -22,17 +21,19 @@ program | |||
22 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') | 21 | .command('watch', 'watch a video in the terminal ✩°。⋆').alias('w') |
23 | .command('repl', 'initiate a REPL to access internals') | 22 | .command('repl', 'initiate a REPL to access internals') |
24 | .command('plugins [action]', 'manage instance plugins/themes').alias('p') | 23 | .command('plugins [action]', 'manage instance plugins/themes').alias('p') |
24 | .command('redundancy [action]', 'manage instance redundancies').alias('r') | ||
25 | 25 | ||
26 | /* Not Yet Implemented */ | 26 | /* Not Yet Implemented */ |
27 | program | 27 | program |
28 | .command('diagnostic [action]', | 28 | .command( |
29 | 'like couple therapy, but for your instance', | 29 | 'diagnostic [action]', |
30 | { noHelp: true } as program.CommandOptions | 30 | 'like couple therapy, but for your instance', |
31 | ).alias('d') | 31 | { noHelp: true } as program.CommandOptions |
32 | ).alias('d') | ||
32 | .command('admin', | 33 | .command('admin', |
33 | 'manage an instance where you have elevated rights', | 34 | 'manage an instance where you have elevated rights', |
34 | { noHelp: true } as program.CommandOptions | 35 | { noHelp: true } as program.CommandOptions |
35 | ).alias('a') | 36 | ).alias('a') |
36 | 37 | ||
37 | // help on no command | 38 | // help on no command |
38 | if (!process.argv.slice(2).length) { | 39 | if (!process.argv.slice(2).length) { |
@@ -81,3 +82,4 @@ getSettings() | |||
81 | }) | 82 | }) |
82 | .parse(process.argv) | 83 | .parse(process.argv) |
83 | }) | 84 | }) |
85 | .catch(err => console.error(err)) | ||
diff --git a/server/tools/yarn.lock b/server/tools/yarn.lock index 28756cbc2..7faa26eaa 100644 --- a/server/tools/yarn.lock +++ b/server/tools/yarn.lock | |||
@@ -2,6 +2,27 @@ | |||
2 | # yarn lockfile v1 | 2 | # yarn lockfile v1 |
3 | 3 | ||
4 | 4 | ||
5 | "@babel/code-frame@^7.0.0": | ||
6 | version "7.8.3" | ||
7 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.8.3.tgz#33e25903d7481181534e12ec0a25f16b6fcf419e" | ||
8 | integrity sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g== | ||
9 | dependencies: | ||
10 | "@babel/highlight" "^7.8.3" | ||
11 | |||
12 | "@babel/helper-validator-identifier@^7.9.0": | ||
13 | version "7.9.0" | ||
14 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.0.tgz#ad53562a7fc29b3b9a91bbf7d10397fd146346ed" | ||
15 | integrity sha512-6G8bQKjOh+of4PV/ThDm/rRqlU7+IGoJuofpagU5GlEl29Vv0RGqqt86ZGRV8ZuSOY3o+8yXl5y782SMcG7SHw== | ||
16 | |||
17 | "@babel/highlight@^7.8.3": | ||
18 | version "7.9.0" | ||
19 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.9.0.tgz#4e9b45ccb82b79607271b2979ad82c7b68163079" | ||
20 | integrity sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ== | ||
21 | dependencies: | ||
22 | "@babel/helper-validator-identifier" "^7.9.0" | ||
23 | chalk "^2.0.0" | ||
24 | js-tokens "^4.0.0" | ||
25 | |||
5 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": | 26 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": |
6 | version "1.1.2" | 27 | version "1.1.2" |
7 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" | 28 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" |
@@ -56,14 +77,14 @@ | |||
56 | integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= | 77 | integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= |
57 | 78 | ||
58 | "@types/long@^4.0.0": | 79 | "@types/long@^4.0.0": |
59 | version "4.0.0" | 80 | version "4.0.1" |
60 | resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef" | 81 | resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" |
61 | integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q== | 82 | integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== |
62 | 83 | ||
63 | "@types/node@^10.1.0": | 84 | "@types/node@^10.1.0": |
64 | version "10.14.22" | 85 | version "10.17.18" |
65 | resolved "https://registry.yarnpkg.com/@types/node/-/node-10.14.22.tgz#34bcdf6b6cb5fc0db33d24816ad9d3ece22feea4" | 86 | resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.18.tgz#ae364d97382aacdebf583fa4e7132af2dfe56a0c" |
66 | integrity sha512-9taxKC944BqoTVjE+UT3pQH0nHZlTvITwfsOZqyc+R3sfJuxaTtxWjfn1K2UlxyPcKHf0rnaXcVFrS9F9vf0bw== | 87 | integrity sha512-DQ2hl/Jl3g33KuAUOcMrcAOtsbzb+y/ufakzAdeK9z/H/xsvkpbETZZbPNMIiQuk24f5ZRMCcZIViAwyFIiKmg== |
67 | 88 | ||
68 | abbrev@1: | 89 | abbrev@1: |
69 | version "1.1.1" | 90 | version "1.1.1" |
@@ -93,18 +114,31 @@ ansi-regex@^3.0.0: | |||
93 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" | 114 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" |
94 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= | 115 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= |
95 | 116 | ||
117 | ansi-regex@^5.0.0: | ||
118 | version "5.0.0" | ||
119 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" | ||
120 | integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== | ||
121 | |||
122 | ansi-styles@^3.2.1: | ||
123 | version "3.2.1" | ||
124 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" | ||
125 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== | ||
126 | dependencies: | ||
127 | color-convert "^1.9.0" | ||
128 | |||
96 | application-config-path@^0.1.0: | 129 | application-config-path@^0.1.0: |
97 | version "0.1.0" | 130 | version "0.1.0" |
98 | resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.0.tgz#193c5f0a86541a4c66fba1e2dc38583362ea5e8f" | 131 | resolved "https://registry.yarnpkg.com/application-config-path/-/application-config-path-0.1.0.tgz#193c5f0a86541a4c66fba1e2dc38583362ea5e8f" |
99 | integrity sha1-GTxfCoZUGkxm+6Hi3DhYM2LqXo8= | 132 | integrity sha1-GTxfCoZUGkxm+6Hi3DhYM2LqXo8= |
100 | 133 | ||
101 | application-config@^1.0.1: | 134 | application-config@^2.0.0: |
102 | version "1.0.1" | 135 | version "2.0.0" |
103 | resolved "https://registry.yarnpkg.com/application-config/-/application-config-1.0.1.tgz#5aa2e2a5ed6abd2e5d1d473d3596f574044fe9e7" | 136 | resolved "https://registry.yarnpkg.com/application-config/-/application-config-2.0.0.tgz#15b4d54d61c0c082f9802227e3e85de876b47747" |
104 | integrity sha1-WqLipe1qvS5dHUc9NZb1dARP6ec= | 137 | integrity sha512-NC5/0guSZK3/UgUDfCk/riByXzqz0owL1L3r63JPSBzYk5QALrp3bLxbsR7qeSfvYfFmAhnp3dbqYsW3U9MpZQ== |
105 | dependencies: | 138 | dependencies: |
106 | application-config-path "^0.1.0" | 139 | application-config-path "^0.1.0" |
107 | mkdirp "^0.5.1" | 140 | load-json-file "^6.2.0" |
141 | write-json-file "^4.2.0" | ||
108 | 142 | ||
109 | aproba@^1.0.3: | 143 | aproba@^1.0.3: |
110 | version "1.2.0" | 144 | version "1.2.0" |
@@ -119,11 +153,6 @@ are-we-there-yet@~1.1.2: | |||
119 | delegates "^1.0.0" | 153 | delegates "^1.0.0" |
120 | readable-stream "^2.0.6" | 154 | readable-stream "^2.0.6" |
121 | 155 | ||
122 | async-limiter@^1.0.0: | ||
123 | version "1.0.1" | ||
124 | resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" | ||
125 | integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== | ||
126 | |||
127 | balanced-match@^1.0.0: | 156 | balanced-match@^1.0.0: |
128 | version "1.0.0" | 157 | version "1.0.0" |
129 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" | 158 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" |
@@ -181,9 +210,9 @@ bittorrent-protocol@^3.0.0: | |||
181 | unordered-array-remove "^1.0.2" | 210 | unordered-array-remove "^1.0.2" |
182 | 211 | ||
183 | bittorrent-tracker@^9.0.0: | 212 | bittorrent-tracker@^9.0.0: |
184 | version "9.14.4" | 213 | version "9.14.5" |
185 | resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.14.4.tgz#0d9661560e6fec37689dfc5045142772eac05536" | 214 | resolved "https://registry.yarnpkg.com/bittorrent-tracker/-/bittorrent-tracker-9.14.5.tgz#aa5573ba91c003581cb337c2889226137f65f32a" |
186 | integrity sha512-2Y/MNRjYhysD6t4r38z7l1WTT7g23IAqRWZRsj7xnnpciFn4xE4qiKmyFwA4gtbFGAZ14K3DdaqZbiQsC3PEfQ== | 215 | integrity sha512-Y1ng5r2qGCgDldjd9eYL8Mv1DjCo6eljqC+T6IMcwmYx0h20KNPKTxJkyNT5gaeJkAhM+p+jmhlV7/ty535Txg== |
187 | dependencies: | 216 | dependencies: |
188 | bencode "^2.0.0" | 217 | bencode "^2.0.0" |
189 | bittorrent-peerid "^1.0.2" | 218 | bittorrent-peerid "^1.0.2" |
@@ -203,7 +232,6 @@ bittorrent-tracker@^9.0.0: | |||
203 | simple-peer "^9.0.0" | 232 | simple-peer "^9.0.0" |
204 | simple-websocket "^8.0.0" | 233 | simple-websocket "^8.0.0" |
205 | string2compact "^1.1.1" | 234 | string2compact "^1.1.1" |
206 | uniq "^1.0.1" | ||
207 | unordered-array-remove "^1.0.2" | 235 | unordered-array-remove "^1.0.2" |
208 | ws "^7.0.0" | 236 | ws "^7.0.0" |
209 | optionalDependencies: | 237 | optionalDependencies: |
@@ -223,9 +251,9 @@ block-stream2@^2.0.0: | |||
223 | readable-stream "^3.4.0" | 251 | readable-stream "^3.4.0" |
224 | 252 | ||
225 | bn.js@^5.0.0: | 253 | bn.js@^5.0.0: |
226 | version "5.0.0" | 254 | version "5.1.1" |
227 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.0.0.tgz#5c3d398021b3ddb548c1296a16f857e908f35c70" | 255 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.1.1.tgz#48efc4031a9c4041b9c99c6941d903463ab62eb5" |
228 | integrity sha512-bVwDX8AF+72fIUNuARelKAlQUNtPOfG2fRxorbVvFk4zpHbqLrPdOGfVg5vrKwVzLLePqPBiATaOZNELQzmS0A== | 256 | integrity sha512-IUTD/REb78Z2eodka1QZyyEk66pciRcP6Sroka0aI3tG/iwIdYLrBD62RsubR7vqdt3WyX8p4jxeatzmRSphtA== |
229 | 257 | ||
230 | brace-expansion@^1.1.7: | 258 | brace-expansion@^1.1.7: |
231 | version "1.1.11" | 259 | version "1.1.11" |
@@ -291,15 +319,24 @@ castv2@~0.1.4: | |||
291 | debug "^4.1.1" | 319 | debug "^4.1.1" |
292 | protobufjs "^6.8.8" | 320 | protobufjs "^6.8.8" |
293 | 321 | ||
322 | chalk@^2.0.0: | ||
323 | version "2.4.2" | ||
324 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" | ||
325 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== | ||
326 | dependencies: | ||
327 | ansi-styles "^3.2.1" | ||
328 | escape-string-regexp "^1.0.5" | ||
329 | supports-color "^5.3.0" | ||
330 | |||
294 | charset@^1.0.1: | 331 | charset@^1.0.1: |
295 | version "1.0.1" | 332 | version "1.0.1" |
296 | resolved "https://registry.yarnpkg.com/charset/-/charset-1.0.1.tgz#8d59546c355be61049a8fa9164747793319852bd" | 333 | resolved "https://registry.yarnpkg.com/charset/-/charset-1.0.1.tgz#8d59546c355be61049a8fa9164747793319852bd" |
297 | integrity sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg== | 334 | integrity sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg== |
298 | 335 | ||
299 | chownr@^1.1.1: | 336 | chownr@^1.1.1: |
300 | version "1.1.3" | 337 | version "1.1.4" |
301 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" | 338 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" |
302 | integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw== | 339 | integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== |
303 | 340 | ||
304 | chrome-dgram@^3.0.2: | 341 | chrome-dgram@^3.0.2: |
305 | version "3.0.4" | 342 | version "3.0.4" |
@@ -347,12 +384,15 @@ chunk-store-stream@^4.0.0: | |||
347 | block-stream2 "^2.0.0" | 384 | block-stream2 "^2.0.0" |
348 | readable-stream "^3.4.0" | 385 | readable-stream "^3.4.0" |
349 | 386 | ||
350 | cli-table@^0.3.1: | 387 | cli-table3@^0.6.0: |
351 | version "0.3.1" | 388 | version "0.6.0" |
352 | resolved "https://registry.yarnpkg.com/cli-table/-/cli-table-0.3.1.tgz#f53b05266a8b1a0b934b3d0821e6e2dc5914ae23" | 389 | resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.0.tgz#b7b1bc65ca8e7b5cef9124e13dc2b21e2ce4faee" |
353 | integrity sha1-9TsFJmqLGguTSz0IIebi3FkUriM= | 390 | integrity sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ== |
354 | dependencies: | 391 | dependencies: |
355 | colors "1.0.3" | 392 | object-assign "^4.1.0" |
393 | string-width "^4.2.0" | ||
394 | optionalDependencies: | ||
395 | colors "^1.1.2" | ||
356 | 396 | ||
357 | clivas@^0.2.0: | 397 | clivas@^0.2.0: |
358 | version "0.2.0" | 398 | version "0.2.0" |
@@ -364,10 +404,22 @@ code-point-at@^1.0.0: | |||
364 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" | 404 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" |
365 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= | 405 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= |
366 | 406 | ||
367 | colors@1.0.3: | 407 | color-convert@^1.9.0: |
368 | version "1.0.3" | 408 | version "1.9.3" |
369 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" | 409 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" |
370 | integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= | 410 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== |
411 | dependencies: | ||
412 | color-name "1.1.3" | ||
413 | |||
414 | color-name@1.1.3: | ||
415 | version "1.1.3" | ||
416 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" | ||
417 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= | ||
418 | |||
419 | colors@^1.1.2: | ||
420 | version "1.4.0" | ||
421 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" | ||
422 | integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== | ||
371 | 423 | ||
372 | common-tags@^1.8.0: | 424 | common-tags@^1.8.0: |
373 | version "1.8.0" | 425 | version "1.8.0" |
@@ -475,18 +527,16 @@ deep-extend@^0.6.0: | |||
475 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" | 527 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" |
476 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== | 528 | integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== |
477 | 529 | ||
478 | define-properties@^1.1.2, define-properties@^1.1.3: | ||
479 | version "1.1.3" | ||
480 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" | ||
481 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== | ||
482 | dependencies: | ||
483 | object-keys "^1.0.12" | ||
484 | |||
485 | delegates@^1.0.0: | 530 | delegates@^1.0.0: |
486 | version "1.0.0" | 531 | version "1.0.0" |
487 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" | 532 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" |
488 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= | 533 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= |
489 | 534 | ||
535 | detect-indent@^6.0.0: | ||
536 | version "6.0.0" | ||
537 | resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" | ||
538 | integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== | ||
539 | |||
490 | detect-libc@^1.0.2: | 540 | detect-libc@^1.0.2: |
491 | version "1.0.3" | 541 | version "1.0.3" |
492 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" | 542 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" |
@@ -529,9 +579,9 @@ domexception@^1.0.1: | |||
529 | webidl-conversions "^4.0.2" | 579 | webidl-conversions "^4.0.2" |
530 | 580 | ||
531 | ecstatic@^4.0.0: | 581 | ecstatic@^4.0.0: |
532 | version "4.1.2" | 582 | version "4.1.4" |
533 | resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-4.1.2.tgz#3afbe29849b32bc2a1f8a90f67e01dc048c7ad40" | 583 | resolved "https://registry.yarnpkg.com/ecstatic/-/ecstatic-4.1.4.tgz#86bf340dabe56c4d0c93d406ac36c040f68e1d79" |
534 | integrity sha512-lnrAOpU2f7Ra8dm1pW0D1ucyUxQIEk8RjFrvROg1YqCV0ueVu9hzgiSEbSyROqXDDiHREdqC4w3AwOTb23P4UQ== | 584 | integrity sha512-8E4ZLK4uRuB9pwywGpy/B9vcz4gCp6IY7u4cMbeCINr/fjb1v+0wf0Ae2XlfSnG8xZYnE4uaJBjFkYI0bqcIdw== |
535 | dependencies: | 585 | dependencies: |
536 | charset "^1.0.1" | 586 | charset "^1.0.1" |
537 | he "^1.1.1" | 587 | he "^1.1.1" |
@@ -552,6 +602,18 @@ elementtree@^0.1.6, elementtree@~0.1.6: | |||
552 | dependencies: | 602 | dependencies: |
553 | sax "1.1.4" | 603 | sax "1.1.4" |
554 | 604 | ||
605 | emoji-regex@^8.0.0: | ||
606 | version "8.0.0" | ||
607 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" | ||
608 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== | ||
609 | |||
610 | end-of-stream@1.4.1: | ||
611 | version "1.4.1" | ||
612 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" | ||
613 | integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== | ||
614 | dependencies: | ||
615 | once "^1.4.0" | ||
616 | |||
555 | end-of-stream@^1.1.0: | 617 | end-of-stream@^1.1.0: |
556 | version "1.4.4" | 618 | version "1.4.4" |
557 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" | 619 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" |
@@ -559,36 +621,23 @@ end-of-stream@^1.1.0: | |||
559 | dependencies: | 621 | dependencies: |
560 | once "^1.4.0" | 622 | once "^1.4.0" |
561 | 623 | ||
562 | es-abstract@^1.5.1: | 624 | error-ex@^1.3.1: |
563 | version "1.16.0" | 625 | version "1.3.2" |
564 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.0.tgz#d3a26dc9c3283ac9750dca569586e976d9dcc06d" | 626 | resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" |
565 | integrity sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg== | 627 | integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== |
566 | dependencies: | ||
567 | es-to-primitive "^1.2.0" | ||
568 | function-bind "^1.1.1" | ||
569 | has "^1.0.3" | ||
570 | has-symbols "^1.0.0" | ||
571 | is-callable "^1.1.4" | ||
572 | is-regex "^1.0.4" | ||
573 | object-inspect "^1.6.0" | ||
574 | object-keys "^1.1.1" | ||
575 | string.prototype.trimleft "^2.1.0" | ||
576 | string.prototype.trimright "^2.1.0" | ||
577 | |||
578 | es-to-primitive@^1.2.0: | ||
579 | version "1.2.0" | ||
580 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" | ||
581 | integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== | ||
582 | dependencies: | 628 | dependencies: |
583 | is-callable "^1.1.4" | 629 | is-arrayish "^0.2.1" |
584 | is-date-object "^1.0.1" | ||
585 | is-symbol "^1.0.2" | ||
586 | 630 | ||
587 | escape-html@^1.0.3: | 631 | escape-html@^1.0.3: |
588 | version "1.0.3" | 632 | version "1.0.3" |
589 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" | 633 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" |
590 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= | 634 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= |
591 | 635 | ||
636 | escape-string-regexp@^1.0.5: | ||
637 | version "1.0.5" | ||
638 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" | ||
639 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= | ||
640 | |||
592 | execa@^0.10.0: | 641 | execa@^0.10.0: |
593 | version "0.10.0" | 642 | version "0.10.0" |
594 | resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" | 643 | resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" |
@@ -645,11 +694,6 @@ fs.realpath@^1.0.0: | |||
645 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" | 694 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" |
646 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= | 695 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= |
647 | 696 | ||
648 | function-bind@^1.1.1: | ||
649 | version "1.1.1" | ||
650 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" | ||
651 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== | ||
652 | |||
653 | gauge@~2.7.3: | 697 | gauge@~2.7.3: |
654 | version "2.7.4" | 698 | version "2.7.4" |
655 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" | 699 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" |
@@ -680,9 +724,9 @@ get-stream@^3.0.0: | |||
680 | integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= | 724 | integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= |
681 | 725 | ||
682 | glob@^7.1.3: | 726 | glob@^7.1.3: |
683 | version "7.1.4" | 727 | version "7.1.6" |
684 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" | 728 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" |
685 | integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== | 729 | integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== |
686 | dependencies: | 730 | dependencies: |
687 | fs.realpath "^1.0.0" | 731 | fs.realpath "^1.0.0" |
688 | inflight "^1.0.4" | 732 | inflight "^1.0.4" |
@@ -691,23 +735,21 @@ glob@^7.1.3: | |||
691 | once "^1.3.0" | 735 | once "^1.3.0" |
692 | path-is-absolute "^1.0.0" | 736 | path-is-absolute "^1.0.0" |
693 | 737 | ||
694 | has-symbols@^1.0.0: | 738 | graceful-fs@^4.1.15: |
695 | version "1.0.0" | 739 | version "4.2.3" |
696 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" | 740 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" |
697 | integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= | 741 | integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== |
742 | |||
743 | has-flag@^3.0.0: | ||
744 | version "3.0.0" | ||
745 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" | ||
746 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= | ||
698 | 747 | ||
699 | has-unicode@^2.0.0: | 748 | has-unicode@^2.0.0: |
700 | version "2.0.1" | 749 | version "2.0.1" |
701 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" | 750 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" |
702 | integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= | 751 | integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= |
703 | 752 | ||
704 | has@^1.0.1, has@^1.0.3: | ||
705 | version "1.0.3" | ||
706 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" | ||
707 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== | ||
708 | dependencies: | ||
709 | function-bind "^1.1.1" | ||
710 | |||
711 | he@^1.1.1: | 753 | he@^1.1.1: |
712 | version "1.2.0" | 754 | version "1.2.0" |
713 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" | 755 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" |
@@ -747,6 +789,11 @@ immediate-chunk-store@^2.0.0: | |||
747 | dependencies: | 789 | dependencies: |
748 | queue-microtask "^1.1.2" | 790 | queue-microtask "^1.1.2" |
749 | 791 | ||
792 | imurmurhash@^0.1.4: | ||
793 | version "0.1.4" | ||
794 | resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" | ||
795 | integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= | ||
796 | |||
750 | inflight@^1.0.4: | 797 | inflight@^1.0.4: |
751 | version "1.0.6" | 798 | version "1.0.6" |
752 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" | 799 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" |
@@ -782,20 +829,20 @@ ip@^1.0.1, ip@^1.1.0, ip@^1.1.3: | |||
782 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" | 829 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" |
783 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== | 830 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== |
784 | 831 | ||
832 | is-arrayish@^0.2.1: | ||
833 | version "0.2.1" | ||
834 | resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" | ||
835 | integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= | ||
836 | |||
785 | is-ascii@^1.0.0: | 837 | is-ascii@^1.0.0: |
786 | version "1.0.0" | 838 | version "1.0.0" |
787 | resolved "https://registry.yarnpkg.com/is-ascii/-/is-ascii-1.0.0.tgz#f02ad0259a0921cd199ff21ce1b09e0f6b4e3929" | 839 | resolved "https://registry.yarnpkg.com/is-ascii/-/is-ascii-1.0.0.tgz#f02ad0259a0921cd199ff21ce1b09e0f6b4e3929" |
788 | integrity sha1-8CrQJZoJIc0Zn/Ic4bCeD2tOOSk= | 840 | integrity sha1-8CrQJZoJIc0Zn/Ic4bCeD2tOOSk= |
789 | 841 | ||
790 | is-callable@^1.1.4: | 842 | is-docker@^2.0.0: |
791 | version "1.1.4" | 843 | version "2.0.0" |
792 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" | 844 | resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.0.0.tgz#2cb0df0e75e2d064fe1864c37cdeacb7b2dcf25b" |
793 | integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== | 845 | integrity sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ== |
794 | |||
795 | is-date-object@^1.0.1: | ||
796 | version "1.0.1" | ||
797 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" | ||
798 | integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= | ||
799 | 846 | ||
800 | is-file@^1.0.0: | 847 | is-file@^1.0.0: |
801 | version "1.0.0" | 848 | version "1.0.0" |
@@ -814,31 +861,27 @@ is-fullwidth-code-point@^2.0.0: | |||
814 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" | 861 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" |
815 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= | 862 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= |
816 | 863 | ||
817 | is-regex@^1.0.4: | 864 | is-fullwidth-code-point@^3.0.0: |
818 | version "1.0.4" | 865 | version "3.0.0" |
819 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" | 866 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" |
820 | integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= | 867 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== |
821 | dependencies: | 868 | |
822 | has "^1.0.1" | 869 | is-plain-obj@^2.0.0: |
870 | version "2.1.0" | ||
871 | resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" | ||
872 | integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== | ||
823 | 873 | ||
824 | is-stream@^1.1.0: | 874 | is-stream@^1.1.0: |
825 | version "1.1.0" | 875 | version "1.1.0" |
826 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" | 876 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" |
827 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= | 877 | integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= |
828 | 878 | ||
829 | is-symbol@^1.0.2: | ||
830 | version "1.0.2" | ||
831 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" | ||
832 | integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== | ||
833 | dependencies: | ||
834 | has-symbols "^1.0.0" | ||
835 | |||
836 | is-typedarray@^1.0.0: | 879 | is-typedarray@^1.0.0: |
837 | version "1.0.0" | 880 | version "1.0.0" |
838 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" | 881 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" |
839 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= | 882 | integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= |
840 | 883 | ||
841 | is-wsl@^2.1.0: | 884 | is-wsl@^2.1.1: |
842 | version "2.1.1" | 885 | version "2.1.1" |
843 | resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" | 886 | resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" |
844 | integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== | 887 | integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== |
@@ -853,6 +896,16 @@ isexe@^2.0.0: | |||
853 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" | 896 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" |
854 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= | 897 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= |
855 | 898 | ||
899 | js-tokens@^4.0.0: | ||
900 | version "4.0.0" | ||
901 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" | ||
902 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== | ||
903 | |||
904 | json-parse-better-errors@^1.0.1: | ||
905 | version "1.0.2" | ||
906 | resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" | ||
907 | integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== | ||
908 | |||
856 | junk@^3.1.0: | 909 | junk@^3.1.0: |
857 | version "3.1.0" | 910 | version "3.1.0" |
858 | resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" | 911 | resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" |
@@ -889,6 +942,11 @@ last-one-wins@^1.0.4: | |||
889 | resolved "https://registry.yarnpkg.com/last-one-wins/-/last-one-wins-1.0.4.tgz#c1bfd0cbcb46790ec9156b8d1aee8fcb86cda22a" | 942 | resolved "https://registry.yarnpkg.com/last-one-wins/-/last-one-wins-1.0.4.tgz#c1bfd0cbcb46790ec9156b8d1aee8fcb86cda22a" |
890 | integrity sha1-wb/Qy8tGeQ7JFWuNGu6Py4bNoio= | 943 | integrity sha1-wb/Qy8tGeQ7JFWuNGu6Py4bNoio= |
891 | 944 | ||
945 | lines-and-columns@^1.1.6: | ||
946 | version "1.1.6" | ||
947 | resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" | ||
948 | integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= | ||
949 | |||
892 | load-ip-set@^2.1.0: | 950 | load-ip-set@^2.1.0: |
893 | version "2.1.0" | 951 | version "2.1.0" |
894 | resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-2.1.0.tgz#2d50b737cae41de4e413d213991d4083a3e1784b" | 952 | resolved "https://registry.yarnpkg.com/load-ip-set/-/load-ip-set-2.1.0.tgz#2d50b737cae41de4e413d213991d4083a3e1784b" |
@@ -900,6 +958,16 @@ load-ip-set@^2.1.0: | |||
900 | simple-get "^3.0.0" | 958 | simple-get "^3.0.0" |
901 | split "^1.0.0" | 959 | split "^1.0.0" |
902 | 960 | ||
961 | load-json-file@^6.2.0: | ||
962 | version "6.2.0" | ||
963 | resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" | ||
964 | integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== | ||
965 | dependencies: | ||
966 | graceful-fs "^4.1.15" | ||
967 | parse-json "^5.0.0" | ||
968 | strip-bom "^4.0.0" | ||
969 | type-fest "^0.6.0" | ||
970 | |||
903 | long@^4.0.0: | 971 | long@^4.0.0: |
904 | version "4.0.0" | 972 | version "4.0.0" |
905 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" | 973 | resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" |
@@ -920,6 +988,13 @@ magnet-uri@^5.1.3: | |||
920 | thirty-two "^1.0.1" | 988 | thirty-two "^1.0.1" |
921 | uniq "^1.0.1" | 989 | uniq "^1.0.1" |
922 | 990 | ||
991 | make-dir@^3.0.0: | ||
992 | version "3.0.2" | ||
993 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.0.2.tgz#04a1acbf22221e1d6ef43559f43e05a90dbb4392" | ||
994 | integrity sha512-rYKABKutXa6vXTXhoV18cBE7PaewPXHe/Bdq4v+ZLMhxbWApkFFplT0LcbMW+6BbjnQXzZ/sAvSE/JdguApG5w== | ||
995 | dependencies: | ||
996 | semver "^6.0.0" | ||
997 | |||
923 | mdns-js-packet@~0.2.0: | 998 | mdns-js-packet@~0.2.0: |
924 | version "0.2.0" | 999 | version "0.2.0" |
925 | resolved "https://registry.yarnpkg.com/mdns-js-packet/-/mdns-js-packet-0.2.0.tgz#642409e8183c7561cc60615bbd1420ec2fad7616" | 1000 | resolved "https://registry.yarnpkg.com/mdns-js-packet/-/mdns-js-packet-0.2.0.tgz#642409e8183c7561cc60615bbd1420ec2fad7616" |
@@ -967,9 +1042,9 @@ mimic-response@^1.0.0: | |||
967 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== | 1042 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== |
968 | 1043 | ||
969 | mimic-response@^2.0.0: | 1044 | mimic-response@^2.0.0: |
970 | version "2.0.0" | 1045 | version "2.1.0" |
971 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.0.0.tgz#996a51c60adf12cb8a87d7fb8ef24c2f3d5ebb46" | 1046 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43" |
972 | integrity sha512-8ilDoEapqA4uQ3TwS0jakGONKXVJqpy+RpM+3b7pLdOjghCrEiGp9SRkFbUHAmZW9vdnrENWHjaweIoTIJExSQ== | 1047 | integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA== |
973 | 1048 | ||
974 | minimatch@^3.0.4: | 1049 | minimatch@^3.0.4: |
975 | version "3.0.4" | 1050 | version "3.0.4" |
@@ -978,15 +1053,10 @@ minimatch@^3.0.4: | |||
978 | dependencies: | 1053 | dependencies: |
979 | brace-expansion "^1.1.7" | 1054 | brace-expansion "^1.1.7" |
980 | 1055 | ||
981 | minimist@0.0.8: | 1056 | minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: |
982 | version "0.0.8" | 1057 | version "1.2.5" |
983 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" | 1058 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" |
984 | integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= | 1059 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== |
985 | |||
986 | minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0: | ||
987 | version "1.2.0" | ||
988 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" | ||
989 | integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= | ||
990 | 1060 | ||
991 | minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: | 1061 | minipass@^2.6.0, minipass@^2.8.6, minipass@^2.9.0: |
992 | version "2.9.0" | 1062 | version "2.9.0" |
@@ -1003,12 +1073,17 @@ minizlib@^1.2.1: | |||
1003 | dependencies: | 1073 | dependencies: |
1004 | minipass "^2.9.0" | 1074 | minipass "^2.9.0" |
1005 | 1075 | ||
1076 | mkdirp-classic@^0.5.2: | ||
1077 | version "0.5.2" | ||
1078 | resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.2.tgz#54c441ce4c96cd7790e10b41a87aa51068ecab2b" | ||
1079 | integrity sha512-ejdnDQcR75gwknmMw/tx02AuRs8jCtqFoFqDZMjiNxsu85sRIJVXDKHuLYvUUPRBUtV2FpSZa9bL1BUa3BdR2g== | ||
1080 | |||
1006 | mkdirp@^0.5.0, mkdirp@^0.5.1: | 1081 | mkdirp@^0.5.0, mkdirp@^0.5.1: |
1007 | version "0.5.1" | 1082 | version "0.5.4" |
1008 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" | 1083 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" |
1009 | integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= | 1084 | integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw== |
1010 | dependencies: | 1085 | dependencies: |
1011 | minimist "0.0.8" | 1086 | minimist "^1.2.5" |
1012 | 1087 | ||
1013 | moment@^2.12.0: | 1088 | moment@^2.12.0: |
1014 | version "2.24.0" | 1089 | version "2.24.0" |
@@ -1057,9 +1132,9 @@ multistream@^4.0.0: | |||
1057 | readable-stream "^3.4.0" | 1132 | readable-stream "^3.4.0" |
1058 | 1133 | ||
1059 | needle@^2.2.1: | 1134 | needle@^2.2.1: |
1060 | version "2.4.0" | 1135 | version "2.3.3" |
1061 | resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c" | 1136 | resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.3.tgz#a041ad1d04a871b0ebb666f40baaf1fb47867117" |
1062 | integrity sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg== | 1137 | integrity sha512-EkY0GeSq87rWp1hoq/sH/wnTWgFVhYlnIkbJ0YJFfRgEFlz2RraCjBpFQ+vrEgEdp0ThfyHADmkChEhcb7PKyw== |
1063 | dependencies: | 1138 | dependencies: |
1064 | debug "^3.2.6" | 1139 | debug "^3.2.6" |
1065 | iconv-lite "^0.4.4" | 1140 | iconv-lite "^0.4.4" |
@@ -1130,25 +1205,33 @@ nodebmc@0.0.7: | |||
1130 | mdns-js "0.5.0" | 1205 | mdns-js "0.5.0" |
1131 | 1206 | ||
1132 | nopt@^4.0.1: | 1207 | nopt@^4.0.1: |
1133 | version "4.0.1" | 1208 | version "4.0.3" |
1134 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" | 1209 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.3.tgz#a375cad9d02fd921278d954c2254d5aa57e15e48" |
1135 | integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= | 1210 | integrity sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg== |
1136 | dependencies: | 1211 | dependencies: |
1137 | abbrev "1" | 1212 | abbrev "1" |
1138 | osenv "^0.1.4" | 1213 | osenv "^0.1.4" |
1139 | 1214 | ||
1140 | npm-bundled@^1.0.1: | 1215 | npm-bundled@^1.0.1: |
1141 | version "1.0.6" | 1216 | version "1.1.1" |
1142 | resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" | 1217 | resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b" |
1143 | integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== | 1218 | integrity sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA== |
1219 | dependencies: | ||
1220 | npm-normalize-package-bin "^1.0.1" | ||
1221 | |||
1222 | npm-normalize-package-bin@^1.0.1: | ||
1223 | version "1.0.1" | ||
1224 | resolved "https://registry.yarnpkg.com/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz#6e79a41f23fd235c0623218228da7d9c23b8f6e2" | ||
1225 | integrity sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA== | ||
1144 | 1226 | ||
1145 | npm-packlist@^1.1.6: | 1227 | npm-packlist@^1.1.6: |
1146 | version "1.4.6" | 1228 | version "1.4.8" |
1147 | resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.6.tgz#53ba3ed11f8523079f1457376dd379ee4ea42ff4" | 1229 | resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e" |
1148 | integrity sha512-u65uQdb+qwtGvEJh/DgQgW1Xg7sqeNbmxYyrvlNznaVTjV3E5P6F/EFjM+BVHXl7JJlsdG8A64M0XI8FI/IOlg== | 1230 | integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A== |
1149 | dependencies: | 1231 | dependencies: |
1150 | ignore-walk "^3.0.1" | 1232 | ignore-walk "^3.0.1" |
1151 | npm-bundled "^1.0.1" | 1233 | npm-bundled "^1.0.1" |
1234 | npm-normalize-package-bin "^1.0.1" | ||
1152 | 1235 | ||
1153 | npm-run-path@^2.0.0: | 1236 | npm-run-path@^2.0.0: |
1154 | version "2.0.2" | 1237 | version "2.0.2" |
@@ -1177,24 +1260,6 @@ object-assign@^4.1.0: | |||
1177 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" | 1260 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" |
1178 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= | 1261 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= |
1179 | 1262 | ||
1180 | object-inspect@^1.6.0: | ||
1181 | version "1.6.0" | ||
1182 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" | ||
1183 | integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== | ||
1184 | |||
1185 | object-keys@^1.0.12, object-keys@^1.1.1: | ||
1186 | version "1.1.1" | ||
1187 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" | ||
1188 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== | ||
1189 | |||
1190 | object.getownpropertydescriptors@^2.0.3: | ||
1191 | version "2.0.3" | ||
1192 | resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" | ||
1193 | integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= | ||
1194 | dependencies: | ||
1195 | define-properties "^1.1.2" | ||
1196 | es-abstract "^1.5.1" | ||
1197 | |||
1198 | on-finished@^2.3.0: | 1263 | on-finished@^2.3.0: |
1199 | version "2.3.0" | 1264 | version "2.3.0" |
1200 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" | 1265 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" |
@@ -1210,11 +1275,12 @@ once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0: | |||
1210 | wrappy "1" | 1275 | wrappy "1" |
1211 | 1276 | ||
1212 | open@^7.0.0: | 1277 | open@^7.0.0: |
1213 | version "7.0.0" | 1278 | version "7.0.3" |
1214 | resolved "https://registry.yarnpkg.com/open/-/open-7.0.0.tgz#7e52999b14eb73f90f0f0807fe93897c4ae73ec9" | 1279 | resolved "https://registry.yarnpkg.com/open/-/open-7.0.3.tgz#db551a1af9c7ab4c7af664139930826138531c48" |
1215 | integrity sha512-K6EKzYqnwQzk+/dzJAQSBORub3xlBTxMz+ntpZpH/LyCa1o6KjXhuN+2npAaI9jaSmU3R1Q8NWf4KUWcyytGsQ== | 1280 | integrity sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA== |
1216 | dependencies: | 1281 | dependencies: |
1217 | is-wsl "^2.1.0" | 1282 | is-docker "^2.0.0" |
1283 | is-wsl "^2.1.1" | ||
1218 | 1284 | ||
1219 | os-homedir@^1.0.0: | 1285 | os-homedir@^1.0.0: |
1220 | version "1.0.2" | 1286 | version "1.0.2" |
@@ -1246,15 +1312,25 @@ package-json-versionify@^1.0.2: | |||
1246 | dependencies: | 1312 | dependencies: |
1247 | browserify-package-json "^1.0.0" | 1313 | browserify-package-json "^1.0.0" |
1248 | 1314 | ||
1315 | parse-json@^5.0.0: | ||
1316 | version "5.0.0" | ||
1317 | resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.0.0.tgz#73e5114c986d143efa3712d4ea24db9a4266f60f" | ||
1318 | integrity sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw== | ||
1319 | dependencies: | ||
1320 | "@babel/code-frame" "^7.0.0" | ||
1321 | error-ex "^1.3.1" | ||
1322 | json-parse-better-errors "^1.0.1" | ||
1323 | lines-and-columns "^1.1.6" | ||
1324 | |||
1249 | parse-numeric-range@^0.0.2: | 1325 | parse-numeric-range@^0.0.2: |
1250 | version "0.0.2" | 1326 | version "0.0.2" |
1251 | resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-0.0.2.tgz#b4f09d413c7adbcd987f6e9233c7b4b210c938e4" | 1327 | resolved "https://registry.yarnpkg.com/parse-numeric-range/-/parse-numeric-range-0.0.2.tgz#b4f09d413c7adbcd987f6e9233c7b4b210c938e4" |
1252 | integrity sha1-tPCdQTx6282Yf26SM8e0shDJOOQ= | 1328 | integrity sha1-tPCdQTx6282Yf26SM8e0shDJOOQ= |
1253 | 1329 | ||
1254 | parse-torrent@^7.0.0: | 1330 | parse-torrent@^7.0.0: |
1255 | version "7.0.1" | 1331 | version "7.1.2" |
1256 | resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-7.0.1.tgz#669c51a95363550055c7de0957741d6a05575daf" | 1332 | resolved "https://registry.yarnpkg.com/parse-torrent/-/parse-torrent-7.1.2.tgz#4ecde4b3be2729ba2b6f336040910d6fe4649d19" |
1257 | integrity sha512-FdF1kBImRLt+ICV4NTz8L+sI2hFlPXAq1tXuw21gKz8EuThyVUFJ/wPfBEyYQrvnBpmGf7cM/LVSOhMRe8MrKw== | 1333 | integrity sha512-1boHRA+aV7aeZBIg0rMBYhtfizAd/BXCXOCh/klYrgVnSpUAuJUIzQrIGkCsb93U1KOVN6C3NZOgpNy8htmqgw== |
1258 | dependencies: | 1334 | dependencies: |
1259 | bencode "^2.0.0" | 1335 | bencode "^2.0.0" |
1260 | blob-to-buffer "^1.2.6" | 1336 | blob-to-buffer "^1.2.6" |
@@ -1262,7 +1338,6 @@ parse-torrent@^7.0.0: | |||
1262 | magnet-uri "^5.1.3" | 1338 | magnet-uri "^5.1.3" |
1263 | simple-get "^3.0.1" | 1339 | simple-get "^3.0.1" |
1264 | simple-sha1 "^3.0.0" | 1340 | simple-sha1 "^3.0.0" |
1265 | uniq "^1.0.1" | ||
1266 | 1341 | ||
1267 | path-is-absolute@^1.0.0: | 1342 | path-is-absolute@^1.0.0: |
1268 | version "1.0.1" | 1343 | version "1.0.1" |
@@ -1303,9 +1378,9 @@ process-nextick-args@~2.0.0: | |||
1303 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== | 1378 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== |
1304 | 1379 | ||
1305 | protobufjs@^6.8.8: | 1380 | protobufjs@^6.8.8: |
1306 | version "6.8.8" | 1381 | version "6.8.9" |
1307 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.8.tgz#c8b4f1282fd7a90e6f5b109ed11c84af82908e7c" | 1382 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.8.9.tgz#0b1adbcdaa983d369c3d9108a97c814edc030754" |
1308 | integrity sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw== | 1383 | integrity sha512-j2JlRdUeL/f4Z6x4aU4gj9I2LECglC+5qR2TrWb193Tla1qfdaNQTZ8I27Pt7K0Ajmvjjpft7O3KWTGciz4gpw== |
1309 | dependencies: | 1384 | dependencies: |
1310 | "@protobufjs/aspromise" "^1.1.2" | 1385 | "@protobufjs/aspromise" "^1.1.2" |
1311 | "@protobufjs/base64" "^1.1.2" | 1386 | "@protobufjs/base64" "^1.1.2" |
@@ -1340,17 +1415,17 @@ queue-microtask@^1.1.0, queue-microtask@^1.1.2: | |||
1340 | integrity sha512-F9wwNePtXrzZenAB3ax0Y8TSKGvuB7Qw16J30hspEUTbfUM+H827XyN3rlpwhVmtm5wuZtbKIHjOnwDn7MUxWQ== | 1415 | integrity sha512-F9wwNePtXrzZenAB3ax0Y8TSKGvuB7Qw16J30hspEUTbfUM+H827XyN3rlpwhVmtm5wuZtbKIHjOnwDn7MUxWQ== |
1341 | 1416 | ||
1342 | random-access-file@^2.0.1: | 1417 | random-access-file@^2.0.1: |
1343 | version "2.1.3" | 1418 | version "2.1.4" |
1344 | resolved "https://registry.yarnpkg.com/random-access-file/-/random-access-file-2.1.3.tgz#642c4b29e39c7dd91609a2e912f174d70fd4f82a" | 1419 | resolved "https://registry.yarnpkg.com/random-access-file/-/random-access-file-2.1.4.tgz#d783e9082d08094c08c6f3dd481f37b2079709dc" |
1345 | integrity sha512-AE0Z1ywR5gIkzACMC1lCsR6LP8g4ynNm7oYWYdKPSSU6Y3H+mGDJxBqfcV9B9KstfHNemhfX3nYmx99ZC9f/yg== | 1420 | integrity sha512-WAcBP5iLhg1pbjZA40WyMenjK7c5gJUY6Pi5HJ3fLJCeVFNSZv3juf20yFMKxBdvcX5GKbX/HZSfFzlLBdGTdQ== |
1346 | dependencies: | 1421 | dependencies: |
1347 | mkdirp "^0.5.1" | 1422 | mkdirp-classic "^0.5.2" |
1348 | random-access-storage "^1.1.1" | 1423 | random-access-storage "^1.1.1" |
1349 | 1424 | ||
1350 | random-access-storage@^1.1.1: | 1425 | random-access-storage@^1.1.1: |
1351 | version "1.4.0" | 1426 | version "1.4.1" |
1352 | resolved "https://registry.yarnpkg.com/random-access-storage/-/random-access-storage-1.4.0.tgz#cbe5b5ccfb38680aac7b78a050d9f0a5ef36841f" | 1427 | resolved "https://registry.yarnpkg.com/random-access-storage/-/random-access-storage-1.4.1.tgz#39a524dd428ade9161ce61a8ae677766e6117ffb" |
1353 | integrity sha512-7oszloM/+PdqWp/oFGyL6SeI14liqo8AAisHAZQGkWdHISyAnngKjNPL0JYIazeLxbHPY6oed2yUffowdq/o6A== | 1428 | integrity sha512-DbCc2TIzOxPaHF6KCbr8zLtiYOJQQQCBHUVNHV/SckUQobCBB2YkDtbLdxGnPwPNpJfEyMWxDAm36A2xkbxxtw== |
1354 | dependencies: | 1429 | dependencies: |
1355 | inherits "^2.0.3" | 1430 | inherits "^2.0.3" |
1356 | 1431 | ||
@@ -1389,9 +1464,9 @@ rc@^1.2.7: | |||
1389 | strip-json-comments "~2.0.1" | 1464 | strip-json-comments "~2.0.1" |
1390 | 1465 | ||
1391 | readable-stream@^2.0.6, readable-stream@^2.2.2: | 1466 | readable-stream@^2.0.6, readable-stream@^2.2.2: |
1392 | version "2.3.6" | 1467 | version "2.3.7" |
1393 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" | 1468 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" |
1394 | integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== | 1469 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== |
1395 | dependencies: | 1470 | dependencies: |
1396 | core-util-is "~1.0.0" | 1471 | core-util-is "~1.0.0" |
1397 | inherits "~2.0.3" | 1472 | inherits "~2.0.3" |
@@ -1402,9 +1477,9 @@ readable-stream@^2.0.6, readable-stream@^2.2.2: | |||
1402 | util-deprecate "~1.0.1" | 1477 | util-deprecate "~1.0.1" |
1403 | 1478 | ||
1404 | readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: | 1479 | readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0: |
1405 | version "3.4.0" | 1480 | version "3.6.0" |
1406 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc" | 1481 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" |
1407 | integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ== | 1482 | integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== |
1408 | dependencies: | 1483 | dependencies: |
1409 | inherits "^2.0.3" | 1484 | inherits "^2.0.3" |
1410 | string_decoder "^1.1.1" | 1485 | string_decoder "^1.1.1" |
@@ -1434,9 +1509,9 @@ rimraf@^2.6.1: | |||
1434 | glob "^7.1.3" | 1509 | glob "^7.1.3" |
1435 | 1510 | ||
1436 | rimraf@^3.0.0: | 1511 | rimraf@^3.0.0: |
1437 | version "3.0.0" | 1512 | version "3.0.2" |
1438 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.0.tgz#614176d4b3010b75e5c390eb0ee96f6dc0cebb9b" | 1513 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" |
1439 | integrity sha512-NDGVxTsjqfunkds7CqsOiEnxln4Bo7Nddl3XhS4pXg5OzwkLqJ971ZVAAnB+DDLnF76N+VnDEiBHaVV8I06SUg== | 1514 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== |
1440 | dependencies: | 1515 | dependencies: |
1441 | glob "^7.1.3" | 1516 | glob "^7.1.3" |
1442 | 1517 | ||
@@ -1490,6 +1565,11 @@ semver@^5.3.0, semver@^5.5.0: | |||
1490 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" | 1565 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" |
1491 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== | 1566 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== |
1492 | 1567 | ||
1568 | semver@^6.0.0: | ||
1569 | version "6.3.0" | ||
1570 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" | ||
1571 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== | ||
1572 | |||
1493 | semver@~5.1.0: | 1573 | semver@~5.1.0: |
1494 | version "5.1.1" | 1574 | version "5.1.1" |
1495 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.1.tgz#a3292a373e6f3e0798da0b20641b9a9c5bc47e19" | 1575 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.1.1.tgz#a3292a373e6f3e0798da0b20641b9a9c5bc47e19" |
@@ -1512,10 +1592,10 @@ shebang-regex@^1.0.0: | |||
1512 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" | 1592 | resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" |
1513 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= | 1593 | integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= |
1514 | 1594 | ||
1515 | signal-exit@^3.0.0: | 1595 | signal-exit@^3.0.0, signal-exit@^3.0.2: |
1516 | version "3.0.2" | 1596 | version "3.0.3" |
1517 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" | 1597 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" |
1518 | integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= | 1598 | integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== |
1519 | 1599 | ||
1520 | simple-concat@^1.0.0: | 1600 | simple-concat@^1.0.0: |
1521 | version "1.0.0" | 1601 | version "1.0.0" |
@@ -1541,9 +1621,9 @@ simple-get@^3.0.0, simple-get@^3.0.1: | |||
1541 | simple-concat "^1.0.0" | 1621 | simple-concat "^1.0.0" |
1542 | 1622 | ||
1543 | simple-peer@^9.0.0: | 1623 | simple-peer@^9.0.0: |
1544 | version "9.6.0" | 1624 | version "9.6.2" |
1545 | resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.6.0.tgz#1560653c2f5360c122f7912cfdb32e8124f5e2c4" | 1625 | resolved "https://registry.yarnpkg.com/simple-peer/-/simple-peer-9.6.2.tgz#42418e77cf8f9184e4fa22ef1017b195c2bf84d7" |
1546 | integrity sha512-NYqSKPu75xhkZYKGJhCbLCG5kfBtDHf8U9ddk4EKFfYNU7XgIisov+V8wMbVVgyMCfn8pm8uOqQQmE50FPDFWA== | 1626 | integrity sha512-EOKoImCaqtNvXIntxT1CBBK/3pVi7tMAoJ3shdyd9qk3zLm3QPiRLb/sPC1G2xvKJkJc5fkQjCXqRZ0AknwTig== |
1547 | dependencies: | 1627 | dependencies: |
1548 | debug "^4.0.1" | 1628 | debug "^4.0.1" |
1549 | get-browser-rtc "^1.0.0" | 1629 | get-browser-rtc "^1.0.0" |
@@ -1560,15 +1640,23 @@ simple-sha1@^3.0.0, simple-sha1@^3.0.1: | |||
1560 | rusha "^0.8.1" | 1640 | rusha "^0.8.1" |
1561 | 1641 | ||
1562 | simple-websocket@^8.0.0: | 1642 | simple-websocket@^8.0.0: |
1563 | version "8.0.1" | 1643 | version "8.1.1" |
1564 | resolved "https://registry.yarnpkg.com/simple-websocket/-/simple-websocket-8.0.1.tgz#c28af779034b329d0cf1448a45fdd311d21fa289" | 1644 | resolved "https://registry.yarnpkg.com/simple-websocket/-/simple-websocket-8.1.1.tgz#4fd68cb1301c1253b2607cfe0950a8be37e6116a" |
1565 | integrity sha512-2QKSRjf+tqFXLVmOQjf95gHeKhuyx2k1ouDjtnE0uKCYw84HfN85HsXo+GmPH+2PIh5BQql++g2AIbHgGAZU4w== | 1645 | integrity sha512-06I3cwOD5Q3LdVd6qfyDGp1U9eau9x9qniSL3b/aDgM5bsJX4nZfCuii2UCFcTfrDq0jCXF4NQ/38qeC8CJZTg== |
1566 | dependencies: | 1646 | dependencies: |
1567 | debug "^4.1.1" | 1647 | debug "^4.1.1" |
1648 | queue-microtask "^1.1.0" | ||
1568 | randombytes "^2.0.3" | 1649 | randombytes "^2.0.3" |
1569 | readable-stream "^3.1.1" | 1650 | readable-stream "^3.1.1" |
1570 | ws "^7.0.0" | 1651 | ws "^7.0.0" |
1571 | 1652 | ||
1653 | sort-keys@^4.0.0: | ||
1654 | version "4.0.0" | ||
1655 | resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-4.0.0.tgz#56dc5e256637bfe3fec8db0dc57c08b1a2be22d6" | ||
1656 | integrity sha512-hlJLzrn/VN49uyNkZ8+9b+0q9DjmmYcYOnbMQtpkLrYpPwRApDPZfmqbUfJnAA3sb/nRib+nDot7Zi/1ER1fuA== | ||
1657 | dependencies: | ||
1658 | is-plain-obj "^2.0.0" | ||
1659 | |||
1572 | speedometer@^1.0.0: | 1660 | speedometer@^1.0.0: |
1573 | version "1.1.0" | 1661 | version "1.1.0" |
1574 | resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-1.1.0.tgz#a30b13abda45687a1a76977012c060f2ac8a7934" | 1662 | resolved "https://registry.yarnpkg.com/speedometer/-/speedometer-1.1.0.tgz#a30b13abda45687a1a76977012c060f2ac8a7934" |
@@ -1617,21 +1705,14 @@ string-width@^1.0.1: | |||
1617 | is-fullwidth-code-point "^2.0.0" | 1705 | is-fullwidth-code-point "^2.0.0" |
1618 | strip-ansi "^4.0.0" | 1706 | strip-ansi "^4.0.0" |
1619 | 1707 | ||
1620 | string.prototype.trimleft@^2.1.0: | 1708 | string-width@^4.2.0: |
1621 | version "2.1.0" | 1709 | version "4.2.0" |
1622 | resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" | 1710 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" |
1623 | integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== | 1711 | integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== |
1624 | dependencies: | ||
1625 | define-properties "^1.1.3" | ||
1626 | function-bind "^1.1.1" | ||
1627 | |||
1628 | string.prototype.trimright@^2.1.0: | ||
1629 | version "2.1.0" | ||
1630 | resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" | ||
1631 | integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== | ||
1632 | dependencies: | 1712 | dependencies: |
1633 | define-properties "^1.1.3" | 1713 | emoji-regex "^8.0.0" |
1634 | function-bind "^1.1.1" | 1714 | is-fullwidth-code-point "^3.0.0" |
1715 | strip-ansi "^6.0.0" | ||
1635 | 1716 | ||
1636 | string2compact@^1.1.1, string2compact@^1.2.5: | 1717 | string2compact@^1.1.1, string2compact@^1.2.5: |
1637 | version "1.3.0" | 1718 | version "1.3.0" |
@@ -1669,6 +1750,18 @@ strip-ansi@^4.0.0: | |||
1669 | dependencies: | 1750 | dependencies: |
1670 | ansi-regex "^3.0.0" | 1751 | ansi-regex "^3.0.0" |
1671 | 1752 | ||
1753 | strip-ansi@^6.0.0: | ||
1754 | version "6.0.0" | ||
1755 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" | ||
1756 | integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== | ||
1757 | dependencies: | ||
1758 | ansi-regex "^5.0.0" | ||
1759 | |||
1760 | strip-bom@^4.0.0: | ||
1761 | version "4.0.0" | ||
1762 | resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" | ||
1763 | integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== | ||
1764 | |||
1672 | strip-eof@^1.0.0: | 1765 | strip-eof@^1.0.0: |
1673 | version "1.0.0" | 1766 | version "1.0.0" |
1674 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" | 1767 | resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" |
@@ -1679,6 +1772,13 @@ strip-json-comments@~2.0.1: | |||
1679 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" | 1772 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" |
1680 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= | 1773 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= |
1681 | 1774 | ||
1775 | supports-color@^5.3.0: | ||
1776 | version "5.5.0" | ||
1777 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" | ||
1778 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== | ||
1779 | dependencies: | ||
1780 | has-flag "^3.0.0" | ||
1781 | |||
1682 | tar@^4: | 1782 | tar@^4: |
1683 | version "4.4.13" | 1783 | version "4.4.13" |
1684 | resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" | 1784 | resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525" |
@@ -1732,7 +1832,12 @@ torrent-piece@^2.0.0: | |||
1732 | resolved "https://registry.yarnpkg.com/torrent-piece/-/torrent-piece-2.0.0.tgz#6598ae67d93699e887f178db267ba16d89d7ec9b" | 1832 | resolved "https://registry.yarnpkg.com/torrent-piece/-/torrent-piece-2.0.0.tgz#6598ae67d93699e887f178db267ba16d89d7ec9b" |
1733 | integrity sha512-H/Z/yCuvZJj1vl1IQHI8dvF2QrUuXRJoptT5DW5967/dsLpXlCg+uyhFR5lfNj5mNaYePUbKtnL+qKWZGXv4Nw== | 1833 | integrity sha512-H/Z/yCuvZJj1vl1IQHI8dvF2QrUuXRJoptT5DW5967/dsLpXlCg+uyhFR5lfNj5mNaYePUbKtnL+qKWZGXv4Nw== |
1734 | 1834 | ||
1735 | typedarray-to-buffer@^3.0.0: | 1835 | type-fest@^0.6.0: |
1836 | version "0.6.0" | ||
1837 | resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" | ||
1838 | integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== | ||
1839 | |||
1840 | typedarray-to-buffer@^3.0.0, typedarray-to-buffer@^3.1.5: | ||
1736 | version "3.1.5" | 1841 | version "3.1.5" |
1737 | resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" | 1842 | resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" |
1738 | integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== | 1843 | integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== |
@@ -1816,14 +1921,6 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: | |||
1816 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" | 1921 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" |
1817 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= | 1922 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= |
1818 | 1923 | ||
1819 | util.promisify@~1.0.0: | ||
1820 | version "1.0.0" | ||
1821 | resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" | ||
1822 | integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== | ||
1823 | dependencies: | ||
1824 | define-properties "^1.1.2" | ||
1825 | object.getownpropertydescriptors "^2.0.3" | ||
1826 | |||
1827 | videostream@^3.2.0: | 1924 | videostream@^3.2.0: |
1828 | version "3.2.1" | 1925 | version "3.2.1" |
1829 | resolved "https://registry.yarnpkg.com/videostream/-/videostream-3.2.1.tgz#643688ad4bfbf37570d421e3196b7e0ad38eeebc" | 1926 | resolved "https://registry.yarnpkg.com/videostream/-/videostream-3.2.1.tgz#643688ad4bfbf37570d421e3196b7e0ad38eeebc" |
@@ -1886,9 +1983,9 @@ webtorrent-hybrid@^4.0.1: | |||
1886 | wrtc "^0.4.1" | 1983 | wrtc "^0.4.1" |
1887 | 1984 | ||
1888 | webtorrent@>=0.107.6: | 1985 | webtorrent@>=0.107.6: |
1889 | version "0.107.16" | 1986 | version "0.108.1" |
1890 | resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-0.107.16.tgz#cf2231f87b3f4334f8eb56ba5aae80df8cd8521c" | 1987 | resolved "https://registry.yarnpkg.com/webtorrent/-/webtorrent-0.108.1.tgz#c5d8ebc538eff85a86deec327b74508ebcf4a371" |
1891 | integrity sha512-5fdPZFiZPxwbigAHtMVQ7ZCXbZSQlxgB6JPD77itpc9DdKYPpliFwCLsNiQpj1jmpo91HlHUJk+Xp3ks1fLUQg== | 1988 | integrity sha512-+w6JaqGKZBZHVrYLmG2VDuRLZlUhQrkLXw0/nw3VKV4aloICWGwBKzjLclXmexUhnqeVzZjCRIQgSZ8+YmgJUQ== |
1892 | dependencies: | 1989 | dependencies: |
1893 | addr-to-ip-port "^1.4.2" | 1990 | addr-to-ip-port "^1.4.2" |
1894 | bitfield "^3.0.0" | 1991 | bitfield "^3.0.0" |
@@ -1898,7 +1995,7 @@ webtorrent@>=0.107.6: | |||
1898 | chunk-store-stream "^4.0.0" | 1995 | chunk-store-stream "^4.0.0" |
1899 | create-torrent "^4.0.0" | 1996 | create-torrent "^4.0.0" |
1900 | debug "^4.1.0" | 1997 | debug "^4.1.0" |
1901 | end-of-stream "^1.1.0" | 1998 | end-of-stream "1.4.1" |
1902 | escape-html "^1.0.3" | 1999 | escape-html "^1.0.3" |
1903 | fs-chunk-store "^2.0.0" | 2000 | fs-chunk-store "^2.0.0" |
1904 | http-node "github:feross/http-node#webtorrent" | 2001 | http-node "github:feross/http-node#webtorrent" |
@@ -1928,7 +2025,6 @@ webtorrent@>=0.107.6: | |||
1928 | stream-with-known-length-to-buffer "^1.0.0" | 2025 | stream-with-known-length-to-buffer "^1.0.0" |
1929 | torrent-discovery "^9.1.1" | 2026 | torrent-discovery "^9.1.1" |
1930 | torrent-piece "^2.0.0" | 2027 | torrent-piece "^2.0.0" |
1931 | uniq "^1.0.1" | ||
1932 | unordered-array-remove "^1.0.2" | 2028 | unordered-array-remove "^1.0.2" |
1933 | ut_metadata "^3.3.0" | 2029 | ut_metadata "^3.3.0" |
1934 | ut_pex "^2.0.0" | 2030 | ut_pex "^2.0.0" |
@@ -1957,29 +2053,48 @@ wrappy@1: | |||
1957 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" | 2053 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" |
1958 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= | 2054 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= |
1959 | 2055 | ||
2056 | write-file-atomic@^3.0.0: | ||
2057 | version "3.0.3" | ||
2058 | resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" | ||
2059 | integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== | ||
2060 | dependencies: | ||
2061 | imurmurhash "^0.1.4" | ||
2062 | is-typedarray "^1.0.0" | ||
2063 | signal-exit "^3.0.2" | ||
2064 | typedarray-to-buffer "^3.1.5" | ||
2065 | |||
2066 | write-json-file@^4.2.0: | ||
2067 | version "4.3.0" | ||
2068 | resolved "https://registry.yarnpkg.com/write-json-file/-/write-json-file-4.3.0.tgz#908493d6fd23225344af324016e4ca8f702dd12d" | ||
2069 | integrity sha512-PxiShnxf0IlnQuMYOPPhPkhExoCQuTUNPOa/2JWCYTmBquU9njyyDuwRKN26IZBlp4yn1nt+Agh2HOOBl+55HQ== | ||
2070 | dependencies: | ||
2071 | detect-indent "^6.0.0" | ||
2072 | graceful-fs "^4.1.15" | ||
2073 | is-plain-obj "^2.0.0" | ||
2074 | make-dir "^3.0.0" | ||
2075 | sort-keys "^4.0.0" | ||
2076 | write-file-atomic "^3.0.0" | ||
2077 | |||
1960 | wrtc@^0.4.1: | 2078 | wrtc@^0.4.1: |
1961 | version "0.4.2" | 2079 | version "0.4.4" |
1962 | resolved "https://registry.yarnpkg.com/wrtc/-/wrtc-0.4.2.tgz#feeb829709dac17139b49f7a18f57f4e98300a6f" | 2080 | resolved "https://registry.yarnpkg.com/wrtc/-/wrtc-0.4.4.tgz#523bcec18ea91de5d8ee1ef11e5cbe4fcf7daf3d" |
1963 | integrity sha512-IaXogllhkd3dHKrZxzQKXmKjqsR35X9XIjp1ElimieZuaPgDCPEbIg/DOwzGTT4bdKqmB8FjPgau2RNMFTGvHQ== | 2081 | integrity sha512-ithsvEKqS6pIbzPiXgJXU4SjQHR7fSszDgGMOREW8j2S4N+ay05r4aYpUZJnsa1fr6o5efeQ/ikFiDXDl5YqeQ== |
1964 | dependencies: | 2082 | dependencies: |
1965 | node-pre-gyp "^0.13.0" | 2083 | node-pre-gyp "^0.13.0" |
1966 | optionalDependencies: | 2084 | optionalDependencies: |
1967 | domexception "^1.0.1" | 2085 | domexception "^1.0.1" |
1968 | 2086 | ||
1969 | ws@^7.0.0: | 2087 | ws@^7.0.0: |
1970 | version "7.2.0" | 2088 | version "7.2.3" |
1971 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.0.tgz#422eda8c02a4b5dba7744ba66eebbd84bcef0ec7" | 2089 | resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46" |
1972 | integrity sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg== | 2090 | integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ== |
1973 | dependencies: | ||
1974 | async-limiter "^1.0.0" | ||
1975 | 2091 | ||
1976 | xml2js@^0.4.8: | 2092 | xml2js@^0.4.8: |
1977 | version "0.4.22" | 2093 | version "0.4.23" |
1978 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.22.tgz#4fa2d846ec803237de86f30aa9b5f70b6600de02" | 2094 | resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" |
1979 | integrity sha512-MWTbxAQqclRSTnehWWe5nMKzI3VmJ8ltiJEco8akcC6j3miOhjjfzKum5sId+CWhfxdOs/1xauYr8/ZDBtQiRw== | 2095 | integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== |
1980 | dependencies: | 2096 | dependencies: |
1981 | sax ">=0.6.0" | 2097 | sax ">=0.6.0" |
1982 | util.promisify "~1.0.0" | ||
1983 | xmlbuilder "~11.0.0" | 2098 | xmlbuilder "~11.0.0" |
1984 | 2099 | ||
1985 | xmlbuilder@0.4.x: | 2100 | xmlbuilder@0.4.x: |
@@ -1993,9 +2108,9 @@ xmlbuilder@~11.0.0: | |||
1993 | integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== | 2108 | integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== |
1994 | 2109 | ||
1995 | xmldom@0.1.x: | 2110 | xmldom@0.1.x: |
1996 | version "0.1.27" | 2111 | version "0.1.31" |
1997 | resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" | 2112 | resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" |
1998 | integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk= | 2113 | integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ== |
1999 | 2114 | ||
2000 | yallist@^3.0.0, yallist@^3.0.3: | 2115 | yallist@^3.0.0, yallist@^3.0.3: |
2001 | version "3.1.1" | 2116 | version "3.1.1" |
diff --git a/server/typings/express.ts b/server/typings/express.ts index 3cc7c7632..5973496f1 100644 --- a/server/typings/express.ts +++ b/server/typings/express.ts | |||
@@ -21,20 +21,38 @@ import { | |||
21 | } from './models' | 21 | } from './models' |
22 | import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist' | 22 | import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist' |
23 | import { MVideoImportDefault } from '@server/typings/models/video/video-import' | 23 | import { MVideoImportDefault } from '@server/typings/models/video/video-import' |
24 | import { MAccountBlocklist, MStreamingPlaylist, MVideoFile } from '@server/typings/models' | 24 | import { MAccountBlocklist, MActorUrl, MStreamingPlaylist, MVideoFile, MVideoImmutable } from '@server/typings/models' |
25 | import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element' | 25 | import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element' |
26 | import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate' | 26 | import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate' |
27 | import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership' | 27 | import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership' |
28 | import { MPlugin, MServer } from '@server/typings/models/server' | 28 | import { MPlugin, MServer } from '@server/typings/models/server' |
29 | import { MServerBlocklist } from './models/server/server-blocklist' | 29 | import { MServerBlocklist } from './models/server/server-blocklist' |
30 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | 30 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' |
31 | import { UserRole } from '@shared/models' | ||
32 | import { RegisterServerAuthExternalOptions } from '@shared/models/plugins/register-server-auth.model' | ||
31 | 33 | ||
32 | declare module 'express' { | 34 | declare module 'express' { |
33 | |||
34 | interface Response { | 35 | interface Response { |
35 | 36 | ||
36 | locals: { | 37 | locals: { |
38 | bypassLogin?: { | ||
39 | bypass: boolean | ||
40 | pluginName: string | ||
41 | authName?: string | ||
42 | user: { | ||
43 | username: string | ||
44 | email: string | ||
45 | displayName: string | ||
46 | role: UserRole | ||
47 | } | ||
48 | } | ||
49 | |||
50 | refreshTokenAuthName?: string | ||
51 | |||
52 | explicitLogout: boolean | ||
53 | |||
37 | videoAll?: MVideoFullLight | 54 | videoAll?: MVideoFullLight |
55 | onlyImmutableVideo?: MVideoImmutable | ||
38 | onlyVideo?: MVideoThumbnail | 56 | onlyVideo?: MVideoThumbnail |
39 | onlyVideoWithRights?: MVideoWithRights | 57 | onlyVideoWithRights?: MVideoWithRights |
40 | videoId?: MVideoIdThumbnail | 58 | videoId?: MVideoIdThumbnail |
@@ -74,6 +92,7 @@ declare module 'express' { | |||
74 | 92 | ||
75 | account?: MAccountDefault | 93 | account?: MAccountDefault |
76 | 94 | ||
95 | actorUrl?: MActorUrl | ||
77 | actorFull?: MActorFull | 96 | actorFull?: MActorFull |
78 | 97 | ||
79 | user?: MUserDefault | 98 | user?: MUserDefault |
@@ -97,6 +116,8 @@ declare module 'express' { | |||
97 | 116 | ||
98 | registeredPlugin?: RegisteredPlugin | 117 | registeredPlugin?: RegisteredPlugin |
99 | 118 | ||
119 | externalAuth?: RegisterServerAuthExternalOptions | ||
120 | |||
100 | plugin?: MPlugin | 121 | plugin?: MPlugin |
101 | } | 122 | } |
102 | } | 123 | } |
diff --git a/server/typings/models/account/account-blocklist.ts b/server/typings/models/account/account-blocklist.ts index c9cb55332..0d8bf11bd 100644 --- a/server/typings/models/account/account-blocklist.ts +++ b/server/typings/models/account/account-blocklist.ts | |||
@@ -12,7 +12,8 @@ export type MAccountBlocklist = Omit<AccountBlocklistModel, 'ByAccount' | 'Block | |||
12 | 12 | ||
13 | export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'> | 13 | export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'> |
14 | 14 | ||
15 | export type MAccountBlocklistAccounts = MAccountBlocklist & | 15 | export type MAccountBlocklistAccounts = |
16 | MAccountBlocklist & | ||
16 | Use<'ByAccount', MAccountDefault> & | 17 | Use<'ByAccount', MAccountDefault> & |
17 | Use<'BlockedAccount', MAccountDefault> | 18 | Use<'BlockedAccount', MAccountDefault> |
18 | 19 | ||
@@ -20,6 +21,7 @@ export type MAccountBlocklistAccounts = MAccountBlocklist & | |||
20 | 21 | ||
21 | // Format for API or AP object | 22 | // Format for API or AP object |
22 | 23 | ||
23 | export type MAccountBlocklistFormattable = Pick<MAccountBlocklist, 'createdAt'> & | 24 | export type MAccountBlocklistFormattable = |
25 | Pick<MAccountBlocklist, 'createdAt'> & | ||
24 | Use<'ByAccount', MAccountFormattable> & | 26 | Use<'ByAccount', MAccountFormattable> & |
25 | Use<'BlockedAccount', MAccountFormattable> | 27 | Use<'BlockedAccount', MAccountFormattable> |
diff --git a/server/typings/models/account/account.ts b/server/typings/models/account/account.ts index adb1f3689..7b826ee04 100644 --- a/server/typings/models/account/account.ts +++ b/server/typings/models/account/account.ts | |||
@@ -21,7 +21,8 @@ type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M> | |||
21 | 21 | ||
22 | // ############################################################################ | 22 | // ############################################################################ |
23 | 23 | ||
24 | export type MAccount = Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' | | 24 | export type MAccount = |
25 | Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' | | ||
25 | 'VideoComments' | 'BlockedAccounts'> | 26 | 'VideoComments' | 'BlockedAccounts'> |
26 | 27 | ||
27 | // ############################################################################ | 28 | // ############################################################################ |
@@ -34,62 +35,75 @@ export type MAccountUserId = Pick<MAccount, 'userId'> | |||
34 | export type MAccountUrl = Use<'Actor', MActorUrl> | 35 | export type MAccountUrl = Use<'Actor', MActorUrl> |
35 | export type MAccountAudience = Use<'Actor', MActorAudience> | 36 | export type MAccountAudience = Use<'Actor', MActorAudience> |
36 | 37 | ||
37 | export type MAccountIdActor = MAccountId & | 38 | export type MAccountIdActor = |
39 | MAccountId & | ||
38 | Use<'Actor', MActor> | 40 | Use<'Actor', MActor> |
39 | 41 | ||
40 | export type MAccountIdActorId = MAccountId & | 42 | export type MAccountIdActorId = |
43 | MAccountId & | ||
41 | Use<'Actor', MActorId> | 44 | Use<'Actor', MActorId> |
42 | 45 | ||
43 | // ############################################################################ | 46 | // ############################################################################ |
44 | 47 | ||
45 | // Default scope | 48 | // Default scope |
46 | export type MAccountDefault = MAccount & | 49 | export type MAccountDefault = |
50 | MAccount & | ||
47 | Use<'Actor', MActorDefault> | 51 | Use<'Actor', MActorDefault> |
48 | 52 | ||
49 | // Default with default association scopes | 53 | // Default with default association scopes |
50 | export type MAccountDefaultChannelDefault = MAccount & | 54 | export type MAccountDefaultChannelDefault = |
55 | MAccount & | ||
51 | Use<'Actor', MActorDefault> & | 56 | Use<'Actor', MActorDefault> & |
52 | Use<'VideoChannels', MChannelDefault[]> | 57 | Use<'VideoChannels', MChannelDefault[]> |
53 | 58 | ||
54 | // We don't need some actors attributes | 59 | // We don't need some actors attributes |
55 | export type MAccountLight = MAccount & | 60 | export type MAccountLight = |
61 | MAccount & | ||
56 | Use<'Actor', MActorDefaultLight> | 62 | Use<'Actor', MActorDefaultLight> |
57 | 63 | ||
58 | // ############################################################################ | 64 | // ############################################################################ |
59 | 65 | ||
60 | // Full actor | 66 | // Full actor |
61 | export type MAccountActor = MAccount & | 67 | export type MAccountActor = |
68 | MAccount & | ||
62 | Use<'Actor', MActor> | 69 | Use<'Actor', MActor> |
63 | 70 | ||
64 | // Full actor with server | 71 | // Full actor with server |
65 | export type MAccountServer = MAccount & | 72 | export type MAccountServer = |
73 | MAccount & | ||
66 | Use<'Actor', MActorServer> | 74 | Use<'Actor', MActorServer> |
67 | 75 | ||
68 | // ############################################################################ | 76 | // ############################################################################ |
69 | 77 | ||
70 | // For API | 78 | // For API |
71 | 79 | ||
72 | export type MAccountSummary = FunctionProperties<MAccount> & | 80 | export type MAccountSummary = |
81 | FunctionProperties<MAccount> & | ||
73 | Pick<MAccount, 'id' | 'name'> & | 82 | Pick<MAccount, 'id' | 'name'> & |
74 | Use<'Actor', MActorSummary> | 83 | Use<'Actor', MActorSummary> |
75 | 84 | ||
76 | export type MAccountSummaryBlocks = MAccountSummary & | 85 | export type MAccountSummaryBlocks = |
86 | MAccountSummary & | ||
77 | Use<'BlockedAccounts', MAccountBlocklistId[]> | 87 | Use<'BlockedAccounts', MAccountBlocklistId[]> |
78 | 88 | ||
79 | export type MAccountAPI = MAccount & | 89 | export type MAccountAPI = |
90 | MAccount & | ||
80 | Use<'Actor', MActorAPI> | 91 | Use<'Actor', MActorAPI> |
81 | 92 | ||
82 | // ############################################################################ | 93 | // ############################################################################ |
83 | 94 | ||
84 | // Format for API or AP object | 95 | // Format for API or AP object |
85 | 96 | ||
86 | export type MAccountSummaryFormattable = FunctionProperties<MAccount> & | 97 | export type MAccountSummaryFormattable = |
98 | FunctionProperties<MAccount> & | ||
87 | Pick<MAccount, 'id' | 'name'> & | 99 | Pick<MAccount, 'id' | 'name'> & |
88 | Use<'Actor', MActorSummaryFormattable> | 100 | Use<'Actor', MActorSummaryFormattable> |
89 | 101 | ||
90 | export type MAccountFormattable = FunctionProperties<MAccount> & | 102 | export type MAccountFormattable = |
103 | FunctionProperties<MAccount> & | ||
91 | Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> & | 104 | Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> & |
92 | Use<'Actor', MActorFormattable> | 105 | Use<'Actor', MActorFormattable> |
93 | 106 | ||
94 | export type MAccountAP = Pick<MAccount, 'name' | 'description'> & | 107 | export type MAccountAP = |
108 | Pick<MAccount, 'name' | 'description'> & | ||
95 | Use<'Actor', MActorAP> | 109 | Use<'Actor', MActorAP> |
diff --git a/server/typings/models/account/actor-follow.ts b/server/typings/models/account/actor-follow.ts index f44157eba..5d0c03c8d 100644 --- a/server/typings/models/account/actor-follow.ts +++ b/server/typings/models/account/actor-follow.ts | |||
@@ -20,22 +20,26 @@ export type MActorFollow = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollow | |||
20 | 20 | ||
21 | // ############################################################################ | 21 | // ############################################################################ |
22 | 22 | ||
23 | export type MActorFollowFollowingHost = MActorFollow & | 23 | export type MActorFollowFollowingHost = |
24 | MActorFollow & | ||
24 | Use<'ActorFollowing', MActorUsername & MActorHost> | 25 | Use<'ActorFollowing', MActorUsername & MActorHost> |
25 | 26 | ||
26 | // ############################################################################ | 27 | // ############################################################################ |
27 | 28 | ||
28 | // With actors or actors default | 29 | // With actors or actors default |
29 | 30 | ||
30 | export type MActorFollowActors = MActorFollow & | 31 | export type MActorFollowActors = |
32 | MActorFollow & | ||
31 | Use<'ActorFollower', MActor> & | 33 | Use<'ActorFollower', MActor> & |
32 | Use<'ActorFollowing', MActor> | 34 | Use<'ActorFollowing', MActor> |
33 | 35 | ||
34 | export type MActorFollowActorsDefault = MActorFollow & | 36 | export type MActorFollowActorsDefault = |
37 | MActorFollow & | ||
35 | Use<'ActorFollower', MActorDefault> & | 38 | Use<'ActorFollower', MActorDefault> & |
36 | Use<'ActorFollowing', MActorDefault> | 39 | Use<'ActorFollowing', MActorDefault> |
37 | 40 | ||
38 | export type MActorFollowFull = MActorFollow & | 41 | export type MActorFollowFull = |
42 | MActorFollow & | ||
39 | Use<'ActorFollower', MActorDefaultAccountChannel> & | 43 | Use<'ActorFollower', MActorDefaultAccountChannel> & |
40 | Use<'ActorFollowing', MActorDefaultAccountChannel> | 44 | Use<'ActorFollowing', MActorDefaultAccountChannel> |
41 | 45 | ||
@@ -43,20 +47,24 @@ export type MActorFollowFull = MActorFollow & | |||
43 | 47 | ||
44 | // For subscriptions | 48 | // For subscriptions |
45 | 49 | ||
46 | type SubscriptionFollowing = MActorDefault & | 50 | type SubscriptionFollowing = |
51 | MActorDefault & | ||
47 | PickWith<ActorModel, 'VideoChannel', MChannelDefault> | 52 | PickWith<ActorModel, 'VideoChannel', MChannelDefault> |
48 | 53 | ||
49 | export type MActorFollowActorsDefaultSubscription = MActorFollow & | 54 | export type MActorFollowActorsDefaultSubscription = |
55 | MActorFollow & | ||
50 | Use<'ActorFollower', MActorDefault> & | 56 | Use<'ActorFollower', MActorDefault> & |
51 | Use<'ActorFollowing', SubscriptionFollowing> | 57 | Use<'ActorFollowing', SubscriptionFollowing> |
52 | 58 | ||
53 | export type MActorFollowSubscriptions = MActorFollow & | 59 | export type MActorFollowSubscriptions = |
60 | MActorFollow & | ||
54 | Use<'ActorFollowing', MActorChannelAccountActor> | 61 | Use<'ActorFollowing', MActorChannelAccountActor> |
55 | 62 | ||
56 | // ############################################################################ | 63 | // ############################################################################ |
57 | 64 | ||
58 | // Format for API or AP object | 65 | // Format for API or AP object |
59 | 66 | ||
60 | export type MActorFollowFormattable = Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> & | 67 | export type MActorFollowFormattable = |
68 | Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> & | ||
61 | Use<'ActorFollower', MActorFormattable> & | 69 | Use<'ActorFollower', MActorFormattable> & |
62 | Use<'ActorFollowing', MActorFormattable> | 70 | Use<'ActorFollowing', MActorFormattable> |
diff --git a/server/typings/models/account/actor.ts b/server/typings/models/account/actor.ts index ee4ece755..1160e84cb 100644 --- a/server/typings/models/account/actor.ts +++ b/server/typings/models/account/actor.ts | |||
@@ -31,18 +31,23 @@ export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'> | |||
31 | export type MActorHost = Use<'Server', MServerHost> | 31 | export type MActorHost = Use<'Server', MServerHost> |
32 | export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed> | 32 | export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed> |
33 | 33 | ||
34 | export type MActorDefaultLight = MActorLight & | 34 | export type MActorDefaultLight = |
35 | MActorLight & | ||
35 | Use<'Server', MServerHost> & | 36 | Use<'Server', MServerHost> & |
36 | Use<'Avatar', MAvatar> | 37 | Use<'Avatar', MAvatar> |
37 | 38 | ||
38 | export type MActorAccountId = MActor & | 39 | export type MActorAccountId = |
40 | MActor & | ||
39 | Use<'Account', MAccountId> | 41 | Use<'Account', MAccountId> |
40 | export type MActorAccountIdActor = MActor & | 42 | export type MActorAccountIdActor = |
43 | MActor & | ||
41 | Use<'Account', MAccountIdActor> | 44 | Use<'Account', MAccountIdActor> |
42 | 45 | ||
43 | export type MActorChannelId = MActor & | 46 | export type MActorChannelId = |
47 | MActor & | ||
44 | Use<'VideoChannel', MChannelId> | 48 | Use<'VideoChannel', MChannelId> |
45 | export type MActorChannelIdActor = MActor & | 49 | export type MActorChannelIdActor = |
50 | MActor & | ||
46 | Use<'VideoChannel', MChannelIdActor> | 51 | Use<'VideoChannel', MChannelIdActor> |
47 | 52 | ||
48 | export type MActorAccountChannelId = MActorAccountId & MActorChannelId | 53 | export type MActorAccountChannelId = MActorAccountId & MActorChannelId |
@@ -52,38 +57,45 @@ export type MActorAccountChannelIdActor = MActorAccountIdActor & MActorChannelId | |||
52 | 57 | ||
53 | // Include raw account/channel/server | 58 | // Include raw account/channel/server |
54 | 59 | ||
55 | export type MActorAccount = MActor & | 60 | export type MActorAccount = |
61 | MActor & | ||
56 | Use<'Account', MAccount> | 62 | Use<'Account', MAccount> |
57 | 63 | ||
58 | export type MActorChannel = MActor & | 64 | export type MActorChannel = |
65 | MActor & | ||
59 | Use<'VideoChannel', MChannel> | 66 | Use<'VideoChannel', MChannel> |
60 | 67 | ||
61 | export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel | 68 | export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel |
62 | 69 | ||
63 | export type MActorServer = MActor & | 70 | export type MActorServer = |
71 | MActor & | ||
64 | Use<'Server', MServer> | 72 | Use<'Server', MServer> |
65 | 73 | ||
66 | // ############################################################################ | 74 | // ############################################################################ |
67 | 75 | ||
68 | // Complex actor associations | 76 | // Complex actor associations |
69 | 77 | ||
70 | export type MActorDefault = MActor & | 78 | export type MActorDefault = |
79 | MActor & | ||
71 | Use<'Server', MServer> & | 80 | Use<'Server', MServer> & |
72 | Use<'Avatar', MAvatar> | 81 | Use<'Avatar', MAvatar> |
73 | 82 | ||
74 | // Actor with channel that is associated to an account and its actor | 83 | // Actor with channel that is associated to an account and its actor |
75 | // Actor -> VideoChannel -> Account -> Actor | 84 | // Actor -> VideoChannel -> Account -> Actor |
76 | export type MActorChannelAccountActor = MActor & | 85 | export type MActorChannelAccountActor = |
86 | MActor & | ||
77 | Use<'VideoChannel', MChannelAccountActor> | 87 | Use<'VideoChannel', MChannelAccountActor> |
78 | 88 | ||
79 | export type MActorFull = MActor & | 89 | export type MActorFull = |
90 | MActor & | ||
80 | Use<'Server', MServer> & | 91 | Use<'Server', MServer> & |
81 | Use<'Avatar', MAvatar> & | 92 | Use<'Avatar', MAvatar> & |
82 | Use<'Account', MAccount> & | 93 | Use<'Account', MAccount> & |
83 | Use<'VideoChannel', MChannelAccountActor> | 94 | Use<'VideoChannel', MChannelAccountActor> |
84 | 95 | ||
85 | // Same than ActorFull, but the account and the channel have their actor | 96 | // Same than ActorFull, but the account and the channel have their actor |
86 | export type MActorFullActor = MActor & | 97 | export type MActorFullActor = |
98 | MActor & | ||
87 | Use<'Server', MServer> & | 99 | Use<'Server', MServer> & |
88 | Use<'Avatar', MAvatar> & | 100 | Use<'Avatar', MAvatar> & |
89 | Use<'Account', MAccountDefault> & | 101 | Use<'Account', MAccountDefault> & |
@@ -93,29 +105,35 @@ export type MActorFullActor = MActor & | |||
93 | 105 | ||
94 | // API | 106 | // API |
95 | 107 | ||
96 | export type MActorSummary = FunctionProperties<MActor> & | 108 | export type MActorSummary = |
109 | FunctionProperties<MActor> & | ||
97 | Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> & | 110 | Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> & |
98 | Use<'Server', MServerHost> & | 111 | Use<'Server', MServerHost> & |
99 | Use<'Avatar', MAvatar> | 112 | Use<'Avatar', MAvatar> |
100 | 113 | ||
101 | export type MActorSummaryBlocks = MActorSummary & | 114 | export type MActorSummaryBlocks = |
115 | MActorSummary & | ||
102 | Use<'Server', MServerHostBlocks> | 116 | Use<'Server', MServerHostBlocks> |
103 | 117 | ||
104 | export type MActorAPI = Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' | | 118 | export type MActorAPI = |
119 | Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' | | ||
105 | 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'> | 120 | 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'> |
106 | 121 | ||
107 | // ############################################################################ | 122 | // ############################################################################ |
108 | 123 | ||
109 | // Format for API or AP object | 124 | // Format for API or AP object |
110 | 125 | ||
111 | export type MActorSummaryFormattable = FunctionProperties<MActor> & | 126 | export type MActorSummaryFormattable = |
127 | FunctionProperties<MActor> & | ||
112 | Pick<MActor, 'url' | 'preferredUsername'> & | 128 | Pick<MActor, 'url' | 'preferredUsername'> & |
113 | Use<'Server', MServerHost> & | 129 | Use<'Server', MServerHost> & |
114 | Use<'Avatar', MAvatarFormattable> | 130 | Use<'Avatar', MAvatarFormattable> |
115 | 131 | ||
116 | export type MActorFormattable = MActorSummaryFormattable & | 132 | export type MActorFormattable = |
133 | MActorSummaryFormattable & | ||
117 | Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> & | 134 | Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> & |
118 | Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>> | 135 | Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>> |
119 | 136 | ||
120 | export type MActorAP = MActor & | 137 | export type MActorAP = |
138 | MActor & | ||
121 | Use<'Avatar', MAvatar> | 139 | Use<'Avatar', MAvatar> |
diff --git a/server/typings/models/account/avatar.ts b/server/typings/models/account/avatar.ts index 8af6cc787..21b47180f 100644 --- a/server/typings/models/account/avatar.ts +++ b/server/typings/models/account/avatar.ts | |||
@@ -7,5 +7,6 @@ export type MAvatar = AvatarModel | |||
7 | 7 | ||
8 | // Format for API or AP object | 8 | // Format for API or AP object |
9 | 9 | ||
10 | export type MAvatarFormattable = FunctionProperties<MAvatar> & | 10 | export type MAvatarFormattable = |
11 | FunctionProperties<MAvatar> & | ||
11 | Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'> | 12 | Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'> |
diff --git a/server/typings/models/oauth/oauth-token.ts b/server/typings/models/oauth/oauth-token.ts index 8ef042d4e..b24a95fd8 100644 --- a/server/typings/models/oauth/oauth-token.ts +++ b/server/typings/models/oauth/oauth-token.ts | |||
@@ -8,6 +8,7 @@ type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M> | |||
8 | 8 | ||
9 | export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'> | 9 | export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'> |
10 | 10 | ||
11 | export type MOAuthTokenUser = MOAuthToken & | 11 | export type MOAuthTokenUser = |
12 | MOAuthToken & | ||
12 | Use<'User', MUserAccountUrl> & | 13 | Use<'User', MUserAccountUrl> & |
13 | { user?: MUserAccountUrl } | 14 | { user?: MUserAccountUrl } |
diff --git a/server/typings/models/server/plugin.ts b/server/typings/models/server/plugin.ts index 94674c318..83eb83794 100644 --- a/server/typings/models/server/plugin.ts +++ b/server/typings/models/server/plugin.ts | |||
@@ -6,5 +6,6 @@ export type MPlugin = PluginModel | |||
6 | 6 | ||
7 | // Format for API or AP object | 7 | // Format for API or AP object |
8 | 8 | ||
9 | export type MPluginFormattable = Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled' | 9 | export type MPluginFormattable = |
10 | Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled' | ||
10 | | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'> | 11 | | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'> |
diff --git a/server/typings/models/server/server-blocklist.ts b/server/typings/models/server/server-blocklist.ts index c3e6230f2..ff6f49176 100644 --- a/server/typings/models/server/server-blocklist.ts +++ b/server/typings/models/server/server-blocklist.ts | |||
@@ -11,7 +11,8 @@ export type MServerBlocklist = Omit<ServerBlocklistModel, 'ByAccount' | 'Blocked | |||
11 | 11 | ||
12 | // ############################################################################ | 12 | // ############################################################################ |
13 | 13 | ||
14 | export type MServerBlocklistAccountServer = MServerBlocklist & | 14 | export type MServerBlocklistAccountServer = |
15 | MServerBlocklist & | ||
15 | Use<'ByAccount', MAccountDefault> & | 16 | Use<'ByAccount', MAccountDefault> & |
16 | Use<'BlockedServer', MServer> | 17 | Use<'BlockedServer', MServer> |
17 | 18 | ||
@@ -19,6 +20,7 @@ export type MServerBlocklistAccountServer = MServerBlocklist & | |||
19 | 20 | ||
20 | // Format for API or AP object | 21 | // Format for API or AP object |
21 | 22 | ||
22 | export type MServerBlocklistFormattable = Pick<MServerBlocklist, 'createdAt'> & | 23 | export type MServerBlocklistFormattable = |
24 | Pick<MServerBlocklist, 'createdAt'> & | ||
23 | Use<'ByAccount', MAccountFormattable> & | 25 | Use<'ByAccount', MAccountFormattable> & |
24 | Use<'BlockedServer', MServerFormattable> | 26 | Use<'BlockedServer', MServerFormattable> |
diff --git a/server/typings/models/server/server.ts b/server/typings/models/server/server.ts index 190cc0c28..b35e55aeb 100644 --- a/server/typings/models/server/server.ts +++ b/server/typings/models/server/server.ts | |||
@@ -13,12 +13,14 @@ export type MServer = Omit<ServerModel, 'Actors' | 'BlockedByAccounts'> | |||
13 | export type MServerHost = Pick<MServer, 'host'> | 13 | export type MServerHost = Pick<MServer, 'host'> |
14 | export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'> | 14 | export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'> |
15 | 15 | ||
16 | export type MServerHostBlocks = MServerHost & | 16 | export type MServerHostBlocks = |
17 | MServerHost & | ||
17 | Use<'BlockedByAccounts', MAccountBlocklistId[]> | 18 | Use<'BlockedByAccounts', MAccountBlocklistId[]> |
18 | 19 | ||
19 | // ############################################################################ | 20 | // ############################################################################ |
20 | 21 | ||
21 | // Format for API or AP object | 22 | // Format for API or AP object |
22 | 23 | ||
23 | export type MServerFormattable = FunctionProperties<MServer> & | 24 | export type MServerFormattable = |
25 | FunctionProperties<MServer> & | ||
24 | Pick<MServer, 'host'> | 26 | Pick<MServer, 'host'> |
diff --git a/server/typings/models/user/user-notification.ts b/server/typings/models/user/user-notification.ts index 1cdc691b0..2080360e1 100644 --- a/server/typings/models/user/user-notification.ts +++ b/server/typings/models/user/user-notification.ts | |||
@@ -16,59 +16,73 @@ type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationMo | |||
16 | 16 | ||
17 | // ############################################################################ | 17 | // ############################################################################ |
18 | 18 | ||
19 | export namespace UserNotificationIncludes { | 19 | export module UserNotificationIncludes { |
20 | |||
20 | export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'> | 21 | export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'> |
21 | export type VideoIncludeChannel = VideoInclude & | 22 | export type VideoIncludeChannel = |
23 | VideoInclude & | ||
22 | PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor> | 24 | PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor> |
23 | 25 | ||
24 | export type ActorInclude = Pick<ActorModel, 'preferredUsername' | 'getHost'> & | 26 | export type ActorInclude = |
27 | Pick<ActorModel, 'preferredUsername' | 'getHost'> & | ||
25 | PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> & | 28 | PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> & |
26 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> | 29 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> |
27 | 30 | ||
28 | export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'> | 31 | export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'> |
29 | export type VideoChannelIncludeActor = VideoChannelInclude & | 32 | export type VideoChannelIncludeActor = |
33 | VideoChannelInclude & | ||
30 | PickWith<VideoChannelModel, 'Actor', ActorInclude> | 34 | PickWith<VideoChannelModel, 'Actor', ActorInclude> |
31 | 35 | ||
32 | export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'> | 36 | export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'> |
33 | export type AccountIncludeActor = AccountInclude & | 37 | export type AccountIncludeActor = |
38 | AccountInclude & | ||
34 | PickWith<AccountModel, 'Actor', ActorInclude> | 39 | PickWith<AccountModel, 'Actor', ActorInclude> |
35 | 40 | ||
36 | export type VideoCommentInclude = Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & | 41 | export type VideoCommentInclude = |
42 | Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & | ||
37 | PickWith<VideoCommentModel, 'Account', AccountIncludeActor> & | 43 | PickWith<VideoCommentModel, 'Account', AccountIncludeActor> & |
38 | PickWith<VideoCommentModel, 'Video', VideoInclude> | 44 | PickWith<VideoCommentModel, 'Video', VideoInclude> |
39 | 45 | ||
40 | export type VideoAbuseInclude = Pick<VideoAbuseModel, 'id'> & | 46 | export type VideoAbuseInclude = |
47 | Pick<VideoAbuseModel, 'id'> & | ||
41 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | 48 | PickWith<VideoAbuseModel, 'Video', VideoInclude> |
42 | 49 | ||
43 | export type VideoBlacklistInclude = Pick<VideoBlacklistModel, 'id'> & | 50 | export type VideoBlacklistInclude = |
51 | Pick<VideoBlacklistModel, 'id'> & | ||
44 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | 52 | PickWith<VideoAbuseModel, 'Video', VideoInclude> |
45 | 53 | ||
46 | export type VideoImportInclude = Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> & | 54 | export type VideoImportInclude = |
55 | Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> & | ||
47 | PickWith<VideoImportModel, 'Video', VideoInclude> | 56 | PickWith<VideoImportModel, 'Video', VideoInclude> |
48 | 57 | ||
49 | export type ActorFollower = Pick<ActorModel, 'preferredUsername' | 'getHost'> & | 58 | export type ActorFollower = |
59 | Pick<ActorModel, 'preferredUsername' | 'getHost'> & | ||
50 | PickWith<ActorModel, 'Account', AccountInclude> & | 60 | PickWith<ActorModel, 'Account', AccountInclude> & |
51 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> & | 61 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> & |
52 | PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> | 62 | PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> |
53 | 63 | ||
54 | export type ActorFollowing = Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> & | 64 | export type ActorFollowing = |
65 | Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> & | ||
55 | PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> & | 66 | PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> & |
56 | PickWith<ActorModel, 'Account', AccountInclude> & | 67 | PickWith<ActorModel, 'Account', AccountInclude> & |
57 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> | 68 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> |
58 | 69 | ||
59 | export type ActorFollowInclude = Pick<ActorFollowModel, 'id' | 'state'> & | 70 | export type ActorFollowInclude = |
71 | Pick<ActorFollowModel, 'id' | 'state'> & | ||
60 | PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> & | 72 | PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> & |
61 | PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing> | 73 | PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing> |
62 | } | 74 | } |
63 | 75 | ||
64 | // ############################################################################ | 76 | // ############################################################################ |
65 | 77 | ||
66 | export type MUserNotification = Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' | | 78 | export type MUserNotification = |
79 | Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' | | ||
67 | 'VideoImport' | 'Account' | 'ActorFollow'> | 80 | 'VideoImport' | 'Account' | 'ActorFollow'> |
68 | 81 | ||
69 | // ############################################################################ | 82 | // ############################################################################ |
70 | 83 | ||
71 | export type UserNotificationModelForApi = MUserNotification & | 84 | export type UserNotificationModelForApi = |
85 | MUserNotification & | ||
72 | Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & | 86 | Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & |
73 | Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & | 87 | Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & |
74 | Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> & | 88 | Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> & |
diff --git a/server/typings/models/user/user.ts b/server/typings/models/user/user.ts index 6ac19c20b..31cf075ef 100644 --- a/server/typings/models/user/user.ts +++ b/server/typings/models/user/user.ts | |||
@@ -29,36 +29,44 @@ export type MUserId = Pick<UserModel, 'id'> | |||
29 | 29 | ||
30 | // With account | 30 | // With account |
31 | 31 | ||
32 | export type MUserAccountId = MUser & | 32 | export type MUserAccountId = |
33 | MUser & | ||
33 | Use<'Account', MAccountId> | 34 | Use<'Account', MAccountId> |
34 | 35 | ||
35 | export type MUserAccountUrl = MUser & | 36 | export type MUserAccountUrl = |
37 | MUser & | ||
36 | Use<'Account', MAccountUrl & MAccountIdActorId> | 38 | Use<'Account', MAccountUrl & MAccountIdActorId> |
37 | 39 | ||
38 | export type MUserAccount = MUser & | 40 | export type MUserAccount = |
41 | MUser & | ||
39 | Use<'Account', MAccount> | 42 | Use<'Account', MAccount> |
40 | 43 | ||
41 | export type MUserAccountDefault = MUser & | 44 | export type MUserAccountDefault = |
45 | MUser & | ||
42 | Use<'Account', MAccountDefault> | 46 | Use<'Account', MAccountDefault> |
43 | 47 | ||
44 | // With channel | 48 | // With channel |
45 | 49 | ||
46 | export type MUserNotifSettingChannelDefault = MUser & | 50 | export type MUserNotifSettingChannelDefault = |
51 | MUser & | ||
47 | Use<'NotificationSetting', MNotificationSetting> & | 52 | Use<'NotificationSetting', MNotificationSetting> & |
48 | Use<'Account', MAccountDefaultChannelDefault> | 53 | Use<'Account', MAccountDefaultChannelDefault> |
49 | 54 | ||
50 | // With notification settings | 55 | // With notification settings |
51 | 56 | ||
52 | export type MUserWithNotificationSetting = MUser & | 57 | export type MUserWithNotificationSetting = |
58 | MUser & | ||
53 | Use<'NotificationSetting', MNotificationSetting> | 59 | Use<'NotificationSetting', MNotificationSetting> |
54 | 60 | ||
55 | export type MUserNotifSettingAccount = MUser & | 61 | export type MUserNotifSettingAccount = |
62 | MUser & | ||
56 | Use<'NotificationSetting', MNotificationSetting> & | 63 | Use<'NotificationSetting', MNotificationSetting> & |
57 | Use<'Account', MAccount> | 64 | Use<'Account', MAccount> |
58 | 65 | ||
59 | // Default scope | 66 | // Default scope |
60 | 67 | ||
61 | export type MUserDefault = MUser & | 68 | export type MUserDefault = |
69 | MUser & | ||
62 | Use<'NotificationSetting', MNotificationSetting> & | 70 | Use<'NotificationSetting', MNotificationSetting> & |
63 | Use<'Account', MAccountDefault> | 71 | Use<'Account', MAccountDefault> |
64 | 72 | ||
@@ -67,12 +75,15 @@ export type MUserDefault = MUser & | |||
67 | // Format for API or AP object | 75 | // Format for API or AP object |
68 | 76 | ||
69 | type MAccountWithChannels = MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]> | 77 | type MAccountWithChannels = MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]> |
70 | type MAccountWithChannelsAndSpecialPlaylists = MAccountWithChannels & | 78 | type MAccountWithChannelsAndSpecialPlaylists = |
79 | MAccountWithChannels & | ||
71 | PickWithOpt<AccountModel, 'VideoPlaylists', MVideoPlaylist[]> | 80 | PickWithOpt<AccountModel, 'VideoPlaylists', MVideoPlaylist[]> |
72 | 81 | ||
73 | export type MUserFormattable = MUserQuotaUsed & | 82 | export type MUserFormattable = |
83 | MUserQuotaUsed & | ||
74 | Use<'Account', MAccountWithChannels> & | 84 | Use<'Account', MAccountWithChannels> & |
75 | PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable> | 85 | PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable> |
76 | 86 | ||
77 | export type MMyUserFormattable = MUserFormattable & | 87 | export type MMyUserFormattable = |
88 | MUserFormattable & | ||
78 | Use<'Account', MAccountWithChannelsAndSpecialPlaylists> | 89 | Use<'Account', MAccountWithChannelsAndSpecialPlaylists> |
diff --git a/server/typings/models/video/schedule-video-update.ts b/server/typings/models/video/schedule-video-update.ts index e6f478cdf..95a53d139 100644 --- a/server/typings/models/video/schedule-video-update.ts +++ b/server/typings/models/video/schedule-video-update.ts | |||
@@ -10,7 +10,8 @@ export type MScheduleVideoUpdate = Omit<ScheduleVideoUpdateModel, 'Video'> | |||
10 | 10 | ||
11 | // ############################################################################ | 11 | // ############################################################################ |
12 | 12 | ||
13 | export type MScheduleVideoUpdateVideoAll = MScheduleVideoUpdate & | 13 | export type MScheduleVideoUpdateVideoAll = |
14 | MScheduleVideoUpdate & | ||
14 | Use<'Video', MVideoAPWithoutCaption & MVideoWithBlacklistLight> | 15 | Use<'Video', MVideoAPWithoutCaption & MVideoWithBlacklistLight> |
15 | 16 | ||
16 | // Format for API or AP object | 17 | // Format for API or AP object |
diff --git a/server/typings/models/video/video-abuse.ts b/server/typings/models/video/video-abuse.ts index e38c3f586..d60f05e4c 100644 --- a/server/typings/models/video/video-abuse.ts +++ b/server/typings/models/video/video-abuse.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 1 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
2 | import { PickWith } from '../../utils' | 2 | import { PickWith } from '../../utils' |
3 | import { MVideo } from './video' | 3 | import { MVideoAccountLightBlacklistAllFiles, MVideo } from './video' |
4 | import { MAccountDefault, MAccountFormattable } from '../account' | 4 | import { MAccountDefault, MAccountFormattable } from '../account' |
5 | 5 | ||
6 | type Use<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M> | 6 | type Use<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M> |
@@ -13,19 +13,23 @@ export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivit | |||
13 | 13 | ||
14 | export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'> | 14 | export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'> |
15 | 15 | ||
16 | export type MVideoAbuseVideo = MVideoAbuse & | 16 | export type MVideoAbuseVideo = |
17 | MVideoAbuse & | ||
17 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | 18 | Pick<VideoAbuseModel, 'toActivityPubObject'> & |
18 | Use<'Video', MVideo> | 19 | Use<'Video', MVideo> |
19 | 20 | ||
20 | export type MVideoAbuseAccountVideo = MVideoAbuse & | 21 | export type MVideoAbuseAccountVideo = |
22 | MVideoAbuse & | ||
21 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | 23 | Pick<VideoAbuseModel, 'toActivityPubObject'> & |
22 | Use<'Video', MVideo> & | 24 | Use<'Video', MVideoAccountLightBlacklistAllFiles> & |
23 | Use<'Account', MAccountDefault> | 25 | Use<'Account', MAccountDefault> |
24 | 26 | ||
25 | // ############################################################################ | 27 | // ############################################################################ |
26 | 28 | ||
27 | // Format for API or AP object | 29 | // Format for API or AP object |
28 | 30 | ||
29 | export type MVideoAbuseFormattable = MVideoAbuse & | 31 | export type MVideoAbuseFormattable = |
32 | MVideoAbuse & | ||
30 | Use<'Account', MAccountFormattable> & | 33 | Use<'Account', MAccountFormattable> & |
31 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>> | 34 | Use<'Video', Pick<MVideoAccountLightBlacklistAllFiles, |
35 | 'id' | 'uuid' | 'name' | 'nsfw' | 'getMiniatureStaticPath' | 'isBlacklisted' | 'VideoChannel'>> | ||
diff --git a/server/typings/models/video/video-blacklist.ts b/server/typings/models/video/video-blacklist.ts index 7122a9dc0..ddb4db832 100644 --- a/server/typings/models/video/video-blacklist.ts +++ b/server/typings/models/video/video-blacklist.ts | |||
@@ -13,15 +13,18 @@ export type MVideoBlacklistUnfederated = Pick<MVideoBlacklist, 'unfederated'> | |||
13 | 13 | ||
14 | // ############################################################################ | 14 | // ############################################################################ |
15 | 15 | ||
16 | export type MVideoBlacklistLightVideo = MVideoBlacklistLight & | 16 | export type MVideoBlacklistLightVideo = |
17 | MVideoBlacklistLight & | ||
17 | Use<'Video', MVideo> | 18 | Use<'Video', MVideo> |
18 | 19 | ||
19 | export type MVideoBlacklistVideo = MVideoBlacklist & | 20 | export type MVideoBlacklistVideo = |
21 | MVideoBlacklist & | ||
20 | Use<'Video', MVideo> | 22 | Use<'Video', MVideo> |
21 | 23 | ||
22 | // ############################################################################ | 24 | // ############################################################################ |
23 | 25 | ||
24 | // Format for API or AP object | 26 | // Format for API or AP object |
25 | 27 | ||
26 | export type MVideoBlacklistFormattable = MVideoBlacklist & | 28 | export type MVideoBlacklistFormattable = |
29 | MVideoBlacklist & | ||
27 | Use<'Video', MVideoFormattable> | 30 | Use<'Video', MVideoFormattable> |
diff --git a/server/typings/models/video/video-caption.ts b/server/typings/models/video/video-caption.ts index ffa56f544..e7aff6956 100644 --- a/server/typings/models/video/video-caption.ts +++ b/server/typings/models/video/video-caption.ts | |||
@@ -11,14 +11,17 @@ export type MVideoCaption = Omit<VideoCaptionModel, 'Video'> | |||
11 | // ############################################################################ | 11 | // ############################################################################ |
12 | 12 | ||
13 | export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'> | 13 | export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'> |
14 | export type MVideoCaptionLanguageUrl = Pick<MVideoCaption, 'language' | 'fileUrl' | 'getFileUrl'> | ||
14 | 15 | ||
15 | export type MVideoCaptionVideo = MVideoCaption & | 16 | export type MVideoCaptionVideo = |
17 | MVideoCaption & | ||
16 | Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>> | 18 | Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>> |
17 | 19 | ||
18 | // ############################################################################ | 20 | // ############################################################################ |
19 | 21 | ||
20 | // Format for API or AP object | 22 | // Format for API or AP object |
21 | 23 | ||
22 | export type MVideoCaptionFormattable = FunctionProperties<MVideoCaption> & | 24 | export type MVideoCaptionFormattable = |
25 | FunctionProperties<MVideoCaption> & | ||
23 | Pick<MVideoCaption, 'language'> & | 26 | Pick<MVideoCaption, 'language'> & |
24 | Use<'Video', MVideoUUID> | 27 | Use<'Video', MVideoUUID> |
diff --git a/server/typings/models/video/video-change-ownership.ts b/server/typings/models/video/video-change-ownership.ts index e5b5bbc1d..971dc3db5 100644 --- a/server/typings/models/video/video-change-ownership.ts +++ b/server/typings/models/video/video-change-ownership.ts | |||
@@ -9,7 +9,8 @@ type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwn | |||
9 | 9 | ||
10 | export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'> | 10 | export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'> |
11 | 11 | ||
12 | export type MVideoChangeOwnershipFull = MVideoChangeOwnership & | 12 | export type MVideoChangeOwnershipFull = |
13 | MVideoChangeOwnership & | ||
13 | Use<'Initiator', MAccountDefault> & | 14 | Use<'Initiator', MAccountDefault> & |
14 | Use<'NextOwner', MAccountDefault> & | 15 | Use<'NextOwner', MAccountDefault> & |
15 | Use<'Video', MVideoWithAllFiles> | 16 | Use<'Video', MVideoWithAllFiles> |
@@ -18,7 +19,8 @@ export type MVideoChangeOwnershipFull = MVideoChangeOwnership & | |||
18 | 19 | ||
19 | // Format for API or AP object | 20 | // Format for API or AP object |
20 | 21 | ||
21 | export type MVideoChangeOwnershipFormattable = Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> & | 22 | export type MVideoChangeOwnershipFormattable = |
23 | Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> & | ||
22 | Use<'Initiator', MAccountFormattable> & | 24 | Use<'Initiator', MAccountFormattable> & |
23 | Use<'NextOwner', MAccountFormattable> & | 25 | Use<'NextOwner', MAccountFormattable> & |
24 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>> | 26 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>> |
diff --git a/server/typings/models/video/video-channels.ts b/server/typings/models/video/video-channels.ts index 292d0ac95..50f7c2d8a 100644 --- a/server/typings/models/video/video-channels.ts +++ b/server/typings/models/video/video-channels.ts | |||
@@ -35,32 +35,39 @@ export type MChannelId = Pick<MChannel, 'id'> | |||
35 | 35 | ||
36 | // ############################################################################ | 36 | // ############################################################################ |
37 | 37 | ||
38 | export type MChannelIdActor = MChannelId & | 38 | export type MChannelIdActor = |
39 | MChannelId & | ||
39 | Use<'Actor', MActorAccountChannelId> | 40 | Use<'Actor', MActorAccountChannelId> |
40 | 41 | ||
41 | export type MChannelUserId = Pick<MChannel, 'accountId'> & | 42 | export type MChannelUserId = |
43 | Pick<MChannel, 'accountId'> & | ||
42 | Use<'Account', MAccountUserId> | 44 | Use<'Account', MAccountUserId> |
43 | 45 | ||
44 | export type MChannelActor = MChannel & | 46 | export type MChannelActor = |
47 | MChannel & | ||
45 | Use<'Actor', MActor> | 48 | Use<'Actor', MActor> |
46 | 49 | ||
47 | export type MChannelUrl = Use<'Actor', MActorUrl> | 50 | export type MChannelUrl = Use<'Actor', MActorUrl> |
48 | 51 | ||
49 | // Default scope | 52 | // Default scope |
50 | export type MChannelDefault = MChannel & | 53 | export type MChannelDefault = |
54 | MChannel & | ||
51 | Use<'Actor', MActorDefault> | 55 | Use<'Actor', MActorDefault> |
52 | 56 | ||
53 | // ############################################################################ | 57 | // ############################################################################ |
54 | 58 | ||
55 | // Not all association attributes | 59 | // Not all association attributes |
56 | 60 | ||
57 | export type MChannelLight = MChannel & | 61 | export type MChannelLight = |
62 | MChannel & | ||
58 | Use<'Actor', MActorDefaultLight> | 63 | Use<'Actor', MActorDefaultLight> |
59 | 64 | ||
60 | export type MChannelActorLight = MChannel & | 65 | export type MChannelActorLight = |
66 | MChannel & | ||
61 | Use<'Actor', MActorLight> | 67 | Use<'Actor', MActorLight> |
62 | 68 | ||
63 | export type MChannelAccountLight = MChannel & | 69 | export type MChannelAccountLight = |
70 | MChannel & | ||
64 | Use<'Actor', MActorDefaultLight> & | 71 | Use<'Actor', MActorDefaultLight> & |
65 | Use<'Account', MAccountLight> | 72 | Use<'Account', MAccountLight> |
66 | 73 | ||
@@ -68,24 +75,29 @@ export type MChannelAccountLight = MChannel & | |||
68 | 75 | ||
69 | // Account associations | 76 | // Account associations |
70 | 77 | ||
71 | export type MChannelAccountActor = MChannel & | 78 | export type MChannelAccountActor = |
79 | MChannel & | ||
72 | Use<'Account', MAccountActor> | 80 | Use<'Account', MAccountActor> |
73 | 81 | ||
74 | export type MChannelAccountDefault = MChannel & | 82 | export type MChannelAccountDefault = |
83 | MChannel & | ||
75 | Use<'Actor', MActorDefault> & | 84 | Use<'Actor', MActorDefault> & |
76 | Use<'Account', MAccountDefault> | 85 | Use<'Account', MAccountDefault> |
77 | 86 | ||
78 | export type MChannelActorAccountActor = MChannel & | 87 | export type MChannelActorAccountActor = |
88 | MChannel & | ||
79 | Use<'Account', MAccountActor> & | 89 | Use<'Account', MAccountActor> & |
80 | Use<'Actor', MActor> | 90 | Use<'Actor', MActor> |
81 | 91 | ||
82 | // ############################################################################ | 92 | // ############################################################################ |
83 | 93 | ||
84 | // Videos associations | 94 | // Videos associations |
85 | export type MChannelVideos = MChannel & | 95 | export type MChannelVideos = |
96 | MChannel & | ||
86 | Use<'Videos', MVideo[]> | 97 | Use<'Videos', MVideo[]> |
87 | 98 | ||
88 | export type MChannelActorAccountDefaultVideos = MChannel & | 99 | export type MChannelActorAccountDefaultVideos = |
100 | MChannel & | ||
89 | Use<'Actor', MActorDefault> & | 101 | Use<'Actor', MActorDefault> & |
90 | Use<'Account', MAccountDefault> & | 102 | Use<'Account', MAccountDefault> & |
91 | Use<'Videos', MVideo[]> | 103 | Use<'Videos', MVideo[]> |
@@ -94,14 +106,17 @@ export type MChannelActorAccountDefaultVideos = MChannel & | |||
94 | 106 | ||
95 | // For API | 107 | // For API |
96 | 108 | ||
97 | export type MChannelSummary = FunctionProperties<MChannel> & | 109 | export type MChannelSummary = |
110 | FunctionProperties<MChannel> & | ||
98 | Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> & | 111 | Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> & |
99 | Use<'Actor', MActorSummary> | 112 | Use<'Actor', MActorSummary> |
100 | 113 | ||
101 | export type MChannelSummaryAccount = MChannelSummary & | 114 | export type MChannelSummaryAccount = |
115 | MChannelSummary & | ||
102 | Use<'Account', MAccountSummaryBlocks> | 116 | Use<'Account', MAccountSummaryBlocks> |
103 | 117 | ||
104 | export type MChannelAPI = MChannel & | 118 | export type MChannelAPI = |
119 | MChannel & | ||
105 | Use<'Actor', MActorAPI> & | 120 | Use<'Actor', MActorAPI> & |
106 | Use<'Account', MAccountAPI> | 121 | Use<'Account', MAccountAPI> |
107 | 122 | ||
@@ -109,18 +124,22 @@ export type MChannelAPI = MChannel & | |||
109 | 124 | ||
110 | // Format for API or AP object | 125 | // Format for API or AP object |
111 | 126 | ||
112 | export type MChannelSummaryFormattable = FunctionProperties<MChannel> & | 127 | export type MChannelSummaryFormattable = |
128 | FunctionProperties<MChannel> & | ||
113 | Pick<MChannel, 'id' | 'name'> & | 129 | Pick<MChannel, 'id' | 'name'> & |
114 | Use<'Actor', MActorSummaryFormattable> | 130 | Use<'Actor', MActorSummaryFormattable> |
115 | 131 | ||
116 | export type MChannelAccountSummaryFormattable = MChannelSummaryFormattable & | 132 | export type MChannelAccountSummaryFormattable = |
133 | MChannelSummaryFormattable & | ||
117 | Use<'Account', MAccountSummaryFormattable> | 134 | Use<'Account', MAccountSummaryFormattable> |
118 | 135 | ||
119 | export type MChannelFormattable = FunctionProperties<MChannel> & | 136 | export type MChannelFormattable = |
137 | FunctionProperties<MChannel> & | ||
120 | Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> & | 138 | Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> & |
121 | Use<'Actor', MActorFormattable> & | 139 | Use<'Actor', MActorFormattable> & |
122 | PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable> | 140 | PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable> |
123 | 141 | ||
124 | export type MChannelAP = Pick<MChannel, 'name' | 'description' | 'support'> & | 142 | export type MChannelAP = |
143 | Pick<MChannel, 'name' | 'description' | 'support'> & | ||
125 | Use<'Actor', MActorAP> & | 144 | Use<'Actor', MActorAP> & |
126 | Use<'Account', MAccountUrl> | 145 | Use<'Account', MAccountUrl> |
diff --git a/server/typings/models/video/video-comment.ts b/server/typings/models/video/video-comment.ts index d693f9186..d6e0b66f5 100644 --- a/server/typings/models/video/video-comment.ts +++ b/server/typings/models/video/video-comment.ts | |||
@@ -14,30 +14,37 @@ export type MCommentUrl = Pick<MComment, 'url'> | |||
14 | 14 | ||
15 | // ############################################################################ | 15 | // ############################################################################ |
16 | 16 | ||
17 | export type MCommentOwner = MComment & | 17 | export type MCommentOwner = |
18 | MComment & | ||
18 | Use<'Account', MAccountDefault> | 19 | Use<'Account', MAccountDefault> |
19 | 20 | ||
20 | export type MCommentVideo = MComment & | 21 | export type MCommentVideo = |
22 | MComment & | ||
21 | Use<'Video', MVideoAccountLight> | 23 | Use<'Video', MVideoAccountLight> |
22 | 24 | ||
23 | export type MCommentReply = MComment & | 25 | export type MCommentReply = |
26 | MComment & | ||
24 | Use<'InReplyToVideoComment', MComment> | 27 | Use<'InReplyToVideoComment', MComment> |
25 | 28 | ||
26 | export type MCommentOwnerVideo = MComment & | 29 | export type MCommentOwnerVideo = |
30 | MComment & | ||
27 | Use<'Account', MAccountDefault> & | 31 | Use<'Account', MAccountDefault> & |
28 | Use<'Video', MVideoAccountLight> | 32 | Use<'Video', MVideoAccountLight> |
29 | 33 | ||
30 | export type MCommentOwnerVideoReply = MComment & | 34 | export type MCommentOwnerVideoReply = |
35 | MComment & | ||
31 | Use<'Account', MAccountDefault> & | 36 | Use<'Account', MAccountDefault> & |
32 | Use<'Video', MVideoAccountLight> & | 37 | Use<'Video', MVideoAccountLight> & |
33 | Use<'InReplyToVideoComment', MComment> | 38 | Use<'InReplyToVideoComment', MComment> |
34 | 39 | ||
35 | export type MCommentOwnerReplyVideoLight = MComment & | 40 | export type MCommentOwnerReplyVideoLight = |
41 | MComment & | ||
36 | Use<'Account', MAccountDefault> & | 42 | Use<'Account', MAccountDefault> & |
37 | Use<'InReplyToVideoComment', MComment> & | 43 | Use<'InReplyToVideoComment', MComment> & |
38 | Use<'Video', MVideoIdUrl> | 44 | Use<'Video', MVideoIdUrl> |
39 | 45 | ||
40 | export type MCommentOwnerVideoFeed = MCommentOwner & | 46 | export type MCommentOwnerVideoFeed = |
47 | MCommentOwner & | ||
41 | Use<'Video', MVideoFeed> | 48 | Use<'Video', MVideoFeed> |
42 | 49 | ||
43 | // ############################################################################ | 50 | // ############################################################################ |
@@ -48,10 +55,12 @@ export type MCommentAPI = MComment & { totalReplies: number } | |||
48 | 55 | ||
49 | // Format for API or AP object | 56 | // Format for API or AP object |
50 | 57 | ||
51 | export type MCommentFormattable = MCommentTotalReplies & | 58 | export type MCommentFormattable = |
59 | MCommentTotalReplies & | ||
52 | Use<'Account', MAccountFormattable> | 60 | Use<'Account', MAccountFormattable> |
53 | 61 | ||
54 | export type MCommentAP = MComment & | 62 | export type MCommentAP = |
63 | MComment & | ||
55 | Use<'Account', MAccountUrl> & | 64 | Use<'Account', MAccountUrl> & |
56 | PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> & | 65 | PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> & |
57 | PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl> | 66 | PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl> |
diff --git a/server/typings/models/video/video-file.ts b/server/typings/models/video/video-file.ts index 352fe3d32..3fcaca78f 100644 --- a/server/typings/models/video/video-file.ts +++ b/server/typings/models/video/video-file.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { VideoFileModel } from '../../../models/video/video-file' | 1 | import { VideoFileModel } from '../../../models/video/video-file' |
2 | import { PickWith, PickWithOpt } from '../../utils' | 2 | import { PickWith, PickWithOpt } from '../../utils' |
3 | import { MVideo, MVideoUUID } from './video' | 3 | import { MVideo, MVideoUUID } from './video' |
4 | import { MVideoRedundancyFileUrl } from './video-redundancy' | 4 | import { MVideoRedundancy, MVideoRedundancyFileUrl } from './video-redundancy' |
5 | import { MStreamingPlaylistVideo, MStreamingPlaylist } from './video-streaming-playlist' | 5 | import { MStreamingPlaylistVideo, MStreamingPlaylist } from './video-streaming-playlist' |
6 | 6 | ||
7 | type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M> | 7 | type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M> |
@@ -10,19 +10,28 @@ type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M> | |||
10 | 10 | ||
11 | export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos' | 'VideoStreamingPlaylist'> | 11 | export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos' | 'VideoStreamingPlaylist'> |
12 | 12 | ||
13 | export type MVideoFileVideo = MVideoFile & | 13 | export type MVideoFileVideo = |
14 | MVideoFile & | ||
14 | Use<'Video', MVideo> | 15 | Use<'Video', MVideo> |
15 | 16 | ||
16 | export type MVideoFileStreamingPlaylist = MVideoFile & | 17 | export type MVideoFileStreamingPlaylist = |
18 | MVideoFile & | ||
17 | Use<'VideoStreamingPlaylist', MStreamingPlaylist> | 19 | Use<'VideoStreamingPlaylist', MStreamingPlaylist> |
18 | 20 | ||
19 | export type MVideoFileStreamingPlaylistVideo = MVideoFile & | 21 | export type MVideoFileStreamingPlaylistVideo = |
22 | MVideoFile & | ||
20 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> | 23 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> |
21 | 24 | ||
22 | export type MVideoFileVideoUUID = MVideoFile & | 25 | export type MVideoFileVideoUUID = |
26 | MVideoFile & | ||
23 | Use<'Video', MVideoUUID> | 27 | Use<'Video', MVideoUUID> |
24 | 28 | ||
25 | export type MVideoFileRedundanciesOpt = MVideoFile & | 29 | export type MVideoFileRedundanciesAll = |
30 | MVideoFile & | ||
31 | PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancy[]> | ||
32 | |||
33 | export type MVideoFileRedundanciesOpt = | ||
34 | MVideoFile & | ||
26 | PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> | 35 | PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> |
27 | 36 | ||
28 | export function isStreamingPlaylistFile (file: any): file is MVideoFileStreamingPlaylist { | 37 | export function isStreamingPlaylistFile (file: any): file is MVideoFileStreamingPlaylist { |
diff --git a/server/typings/models/video/video-import.ts b/server/typings/models/video/video-import.ts index e119f17f9..4e5c2e4f0 100644 --- a/server/typings/models/video/video-import.ts +++ b/server/typings/models/video/video-import.ts | |||
@@ -9,18 +9,21 @@ type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M> | |||
9 | 9 | ||
10 | export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'> | 10 | export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'> |
11 | 11 | ||
12 | export type MVideoImportVideo = MVideoImport & | 12 | export type MVideoImportVideo = |
13 | MVideoImport & | ||
13 | Use<'Video', MVideo> | 14 | Use<'Video', MVideo> |
14 | 15 | ||
15 | // ############################################################################ | 16 | // ############################################################################ |
16 | 17 | ||
17 | type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail | 18 | type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail |
18 | 19 | ||
19 | export type MVideoImportDefault = MVideoImport & | 20 | export type MVideoImportDefault = |
21 | MVideoImport & | ||
20 | Use<'User', MUser> & | 22 | Use<'User', MUser> & |
21 | Use<'Video', VideoAssociation> | 23 | Use<'Video', VideoAssociation> |
22 | 24 | ||
23 | export type MVideoImportDefaultFiles = MVideoImport & | 25 | export type MVideoImportDefaultFiles = |
26 | MVideoImport & | ||
24 | Use<'User', MUser> & | 27 | Use<'User', MUser> & |
25 | Use<'Video', VideoAssociation & MVideoWithFile> | 28 | Use<'Video', VideoAssociation & MVideoWithFile> |
26 | 29 | ||
@@ -28,5 +31,6 @@ export type MVideoImportDefaultFiles = MVideoImport & | |||
28 | 31 | ||
29 | // Format for API or AP object | 32 | // Format for API or AP object |
30 | 33 | ||
31 | export type MVideoImportFormattable = MVideoImport & | 34 | export type MVideoImportFormattable = |
35 | MVideoImport & | ||
32 | PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag> | 36 | PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag> |
diff --git a/server/typings/models/video/video-playlist-element.ts b/server/typings/models/video/video-playlist-element.ts index 1aeff78d8..f33c76594 100644 --- a/server/typings/models/video/video-playlist-element.ts +++ b/server/typings/models/video/video-playlist-element.ts | |||
@@ -17,10 +17,12 @@ export type MVideoPlaylistElementLight = Pick<MVideoPlaylistElement, 'id' | 'vid | |||
17 | 17 | ||
18 | // ############################################################################ | 18 | // ############################################################################ |
19 | 19 | ||
20 | export type MVideoPlaylistVideoThumbnail = MVideoPlaylistElement & | 20 | export type MVideoPlaylistVideoThumbnail = |
21 | MVideoPlaylistElement & | ||
21 | Use<'Video', MVideoThumbnail> | 22 | Use<'Video', MVideoThumbnail> |
22 | 23 | ||
23 | export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement & | 24 | export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = |
25 | MVideoPlaylistElement & | ||
24 | Use<'Video', MVideoUrl> & | 26 | Use<'Video', MVideoUrl> & |
25 | Use<'VideoPlaylist', MVideoPlaylistPrivacy> | 27 | Use<'VideoPlaylist', MVideoPlaylistPrivacy> |
26 | 28 | ||
@@ -28,8 +30,10 @@ export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement | |||
28 | 30 | ||
29 | // Format for API or AP object | 31 | // Format for API or AP object |
30 | 32 | ||
31 | export type MVideoPlaylistElementFormattable = MVideoPlaylistElement & | 33 | export type MVideoPlaylistElementFormattable = |
34 | MVideoPlaylistElement & | ||
32 | Use<'Video', MVideoFormattable> | 35 | Use<'Video', MVideoFormattable> |
33 | 36 | ||
34 | export type MVideoPlaylistElementAP = MVideoPlaylistElement & | 37 | export type MVideoPlaylistElementAP = |
38 | MVideoPlaylistElement & | ||
35 | Use<'Video', MVideoUrl> | 39 | Use<'Video', MVideoUrl> |
diff --git a/server/typings/models/video/video-playlist.ts b/server/typings/models/video/video-playlist.ts index a40c7aca9..49c27f4a7 100644 --- a/server/typings/models/video/video-playlist.ts +++ b/server/typings/models/video/video-playlist.ts | |||
@@ -22,30 +22,36 @@ export type MVideoPlaylistVideosLength = MVideoPlaylist & { videosLength?: numbe | |||
22 | 22 | ||
23 | // With elements | 23 | // With elements |
24 | 24 | ||
25 | export type MVideoPlaylistWithElements = MVideoPlaylist & | 25 | export type MVideoPlaylistWithElements = |
26 | MVideoPlaylist & | ||
26 | Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> | 27 | Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> |
27 | 28 | ||
28 | export type MVideoPlaylistIdWithElements = MVideoPlaylistId & | 29 | export type MVideoPlaylistIdWithElements = |
30 | MVideoPlaylistId & | ||
29 | Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> | 31 | Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> |
30 | 32 | ||
31 | // ############################################################################ | 33 | // ############################################################################ |
32 | 34 | ||
33 | // With account | 35 | // With account |
34 | 36 | ||
35 | export type MVideoPlaylistOwner = MVideoPlaylist & | 37 | export type MVideoPlaylistOwner = |
38 | MVideoPlaylist & | ||
36 | Use<'OwnerAccount', MAccount> | 39 | Use<'OwnerAccount', MAccount> |
37 | 40 | ||
38 | export type MVideoPlaylistOwnerDefault = MVideoPlaylist & | 41 | export type MVideoPlaylistOwnerDefault = |
42 | MVideoPlaylist & | ||
39 | Use<'OwnerAccount', MAccountDefault> | 43 | Use<'OwnerAccount', MAccountDefault> |
40 | 44 | ||
41 | // ############################################################################ | 45 | // ############################################################################ |
42 | 46 | ||
43 | // With thumbnail | 47 | // With thumbnail |
44 | 48 | ||
45 | export type MVideoPlaylistThumbnail = MVideoPlaylist & | 49 | export type MVideoPlaylistThumbnail = |
50 | MVideoPlaylist & | ||
46 | Use<'Thumbnail', MThumbnail> | 51 | Use<'Thumbnail', MThumbnail> |
47 | 52 | ||
48 | export type MVideoPlaylistAccountThumbnail = MVideoPlaylist & | 53 | export type MVideoPlaylistAccountThumbnail = |
54 | MVideoPlaylist & | ||
49 | Use<'OwnerAccount', MAccountDefault> & | 55 | Use<'OwnerAccount', MAccountDefault> & |
50 | Use<'Thumbnail', MThumbnail> | 56 | Use<'Thumbnail', MThumbnail> |
51 | 57 | ||
@@ -53,7 +59,8 @@ export type MVideoPlaylistAccountThumbnail = MVideoPlaylist & | |||
53 | 59 | ||
54 | // With channel | 60 | // With channel |
55 | 61 | ||
56 | export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist & | 62 | export type MVideoPlaylistAccountChannelDefault = |
63 | MVideoPlaylist & | ||
57 | Use<'OwnerAccount', MAccountDefault> & | 64 | Use<'OwnerAccount', MAccountDefault> & |
58 | Use<'VideoChannel', MChannelDefault> | 65 | Use<'VideoChannel', MChannelDefault> |
59 | 66 | ||
@@ -61,7 +68,8 @@ export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist & | |||
61 | 68 | ||
62 | // With all associations | 69 | // With all associations |
63 | 70 | ||
64 | export type MVideoPlaylistFull = MVideoPlaylist & | 71 | export type MVideoPlaylistFull = |
72 | MVideoPlaylist & | ||
65 | Use<'OwnerAccount', MAccountDefault> & | 73 | Use<'OwnerAccount', MAccountDefault> & |
66 | Use<'VideoChannel', MChannelDefault> & | 74 | Use<'VideoChannel', MChannelDefault> & |
67 | Use<'Thumbnail', MThumbnail> | 75 | Use<'Thumbnail', MThumbnail> |
@@ -70,11 +78,13 @@ export type MVideoPlaylistFull = MVideoPlaylist & | |||
70 | 78 | ||
71 | // For API | 79 | // For API |
72 | 80 | ||
73 | export type MVideoPlaylistAccountChannelSummary = MVideoPlaylist & | 81 | export type MVideoPlaylistAccountChannelSummary = |
82 | MVideoPlaylist & | ||
74 | Use<'OwnerAccount', MAccountSummary> & | 83 | Use<'OwnerAccount', MAccountSummary> & |
75 | Use<'VideoChannel', MChannelSummary> | 84 | Use<'VideoChannel', MChannelSummary> |
76 | 85 | ||
77 | export type MVideoPlaylistFullSummary = MVideoPlaylist & | 86 | export type MVideoPlaylistFullSummary = |
87 | MVideoPlaylist & | ||
78 | Use<'Thumbnail', MThumbnail> & | 88 | Use<'Thumbnail', MThumbnail> & |
79 | Use<'OwnerAccount', MAccountSummary> & | 89 | Use<'OwnerAccount', MAccountSummary> & |
80 | Use<'VideoChannel', MChannelSummary> | 90 | Use<'VideoChannel', MChannelSummary> |
@@ -83,10 +93,12 @@ export type MVideoPlaylistFullSummary = MVideoPlaylist & | |||
83 | 93 | ||
84 | // Format for API or AP object | 94 | // Format for API or AP object |
85 | 95 | ||
86 | export type MVideoPlaylistFormattable = MVideoPlaylistVideosLength & | 96 | export type MVideoPlaylistFormattable = |
97 | MVideoPlaylistVideosLength & | ||
87 | Use<'OwnerAccount', MAccountSummaryFormattable> & | 98 | Use<'OwnerAccount', MAccountSummaryFormattable> & |
88 | Use<'VideoChannel', MChannelSummaryFormattable> | 99 | Use<'VideoChannel', MChannelSummaryFormattable> |
89 | 100 | ||
90 | export type MVideoPlaylistAP = MVideoPlaylist & | 101 | export type MVideoPlaylistAP = |
102 | MVideoPlaylist & | ||
91 | Use<'Thumbnail', MThumbnail> & | 103 | Use<'Thumbnail', MThumbnail> & |
92 | Use<'VideoChannel', MChannelUrl> | 104 | Use<'VideoChannel', MChannelUrl> |
diff --git a/server/typings/models/video/video-rate.ts b/server/typings/models/video/video-rate.ts index f6bb527fc..64ce4965b 100644 --- a/server/typings/models/video/video-rate.ts +++ b/server/typings/models/video/video-rate.ts | |||
@@ -9,10 +9,12 @@ type Use<K extends keyof AccountVideoRateModel, M> = PickWith<AccountVideoRateMo | |||
9 | 9 | ||
10 | export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'> | 10 | export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'> |
11 | 11 | ||
12 | export type MAccountVideoRateAccountUrl = MAccountVideoRate & | 12 | export type MAccountVideoRateAccountUrl = |
13 | MAccountVideoRate & | ||
13 | Use<'Account', MAccountUrl> | 14 | Use<'Account', MAccountUrl> |
14 | 15 | ||
15 | export type MAccountVideoRateAccountVideo = MAccountVideoRate & | 16 | export type MAccountVideoRateAccountVideo = |
17 | MAccountVideoRate & | ||
16 | Use<'Account', MAccountAudience> & | 18 | Use<'Account', MAccountAudience> & |
17 | Use<'Video', MVideo> | 19 | Use<'Video', MVideo> |
18 | 20 | ||
@@ -20,5 +22,6 @@ export type MAccountVideoRateAccountVideo = MAccountVideoRate & | |||
20 | 22 | ||
21 | // Format for API or AP object | 23 | // Format for API or AP object |
22 | 24 | ||
23 | export type MAccountVideoRateFormattable = Pick<MAccountVideoRate, 'type'> & | 25 | export type MAccountVideoRateFormattable = |
26 | Pick<MAccountVideoRate, 'type'> & | ||
24 | Use<'Video', MVideoFormattable> | 27 | Use<'Video', MVideoFormattable> |
diff --git a/server/typings/models/video/video-redundancy.ts b/server/typings/models/video/video-redundancy.ts index 25bdac057..5107aa7f4 100644 --- a/server/typings/models/video/video-redundancy.ts +++ b/server/typings/models/video/video-redundancy.ts | |||
@@ -16,16 +16,20 @@ export type MVideoRedundancyFileUrl = Pick<MVideoRedundancy, 'fileUrl'> | |||
16 | 16 | ||
17 | // ############################################################################ | 17 | // ############################################################################ |
18 | 18 | ||
19 | export type MVideoRedundancyFile = MVideoRedundancy & | 19 | export type MVideoRedundancyFile = |
20 | MVideoRedundancy & | ||
20 | Use<'VideoFile', MVideoFile> | 21 | Use<'VideoFile', MVideoFile> |
21 | 22 | ||
22 | export type MVideoRedundancyFileVideo = MVideoRedundancy & | 23 | export type MVideoRedundancyFileVideo = |
24 | MVideoRedundancy & | ||
23 | Use<'VideoFile', MVideoFileVideo> | 25 | Use<'VideoFile', MVideoFileVideo> |
24 | 26 | ||
25 | export type MVideoRedundancyStreamingPlaylistVideo = MVideoRedundancy & | 27 | export type MVideoRedundancyStreamingPlaylistVideo = |
28 | MVideoRedundancy & | ||
26 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> | 29 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> |
27 | 30 | ||
28 | export type MVideoRedundancyVideo = MVideoRedundancy & | 31 | export type MVideoRedundancyVideo = |
32 | MVideoRedundancy & | ||
29 | Use<'VideoFile', MVideoFileVideo> & | 33 | Use<'VideoFile', MVideoFileVideo> & |
30 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> | 34 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> |
31 | 35 | ||
@@ -33,6 +37,7 @@ export type MVideoRedundancyVideo = MVideoRedundancy & | |||
33 | 37 | ||
34 | // Format for API or AP object | 38 | // Format for API or AP object |
35 | 39 | ||
36 | export type MVideoRedundancyAP = MVideoRedundancy & | 40 | export type MVideoRedundancyAP = |
41 | MVideoRedundancy & | ||
37 | PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> & | 42 | PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> & |
38 | PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>> | 43 | PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>> |
diff --git a/server/typings/models/video/video-share.ts b/server/typings/models/video/video-share.ts index a7a90beeb..50ca75d26 100644 --- a/server/typings/models/video/video-share.ts +++ b/server/typings/models/video/video-share.ts | |||
@@ -9,9 +9,11 @@ type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M> | |||
9 | 9 | ||
10 | export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'> | 10 | export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'> |
11 | 11 | ||
12 | export type MVideoShareActor = MVideoShare & | 12 | export type MVideoShareActor = |
13 | MVideoShare & | ||
13 | Use<'Actor', MActorDefault> | 14 | Use<'Actor', MActorDefault> |
14 | 15 | ||
15 | export type MVideoShareFull = MVideoShare & | 16 | export type MVideoShareFull = |
17 | MVideoShare & | ||
16 | Use<'Actor', MActorDefault> & | 18 | Use<'Actor', MActorDefault> & |
17 | Use<'Video', MVideo> | 19 | Use<'Video', MVideo> |
diff --git a/server/typings/models/video/video-streaming-playlist.ts b/server/typings/models/video/video-streaming-playlist.ts index 436c0c072..3f54aa560 100644 --- a/server/typings/models/video/video-streaming-playlist.ts +++ b/server/typings/models/video/video-streaming-playlist.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist' | 1 | import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist' |
2 | import { PickWith, PickWithOpt } from '../../utils' | 2 | import { PickWith, PickWithOpt } from '../../utils' |
3 | import { MVideoRedundancyFileUrl } from './video-redundancy' | 3 | import { MVideoRedundancyFileUrl, MVideoRedundancy } from './video-redundancy' |
4 | import { MVideo } from './video' | 4 | import { MVideo } from './video' |
5 | import { MVideoFile } from './video-file' | 5 | import { MVideoFile } from './video-file' |
6 | 6 | ||
@@ -10,21 +10,31 @@ type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreami | |||
10 | 10 | ||
11 | export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos' | 'VideoFiles'> | 11 | export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos' | 'VideoFiles'> |
12 | 12 | ||
13 | export type MStreamingPlaylistFiles = MStreamingPlaylist & | 13 | export type MStreamingPlaylistFiles = |
14 | MStreamingPlaylist & | ||
14 | Use<'VideoFiles', MVideoFile[]> | 15 | Use<'VideoFiles', MVideoFile[]> |
15 | 16 | ||
16 | export type MStreamingPlaylistVideo = MStreamingPlaylist & | 17 | export type MStreamingPlaylistVideo = |
18 | MStreamingPlaylist & | ||
17 | Use<'Video', MVideo> | 19 | Use<'Video', MVideo> |
18 | 20 | ||
19 | export type MStreamingPlaylistFilesVideo = MStreamingPlaylist & | 21 | export type MStreamingPlaylistFilesVideo = |
22 | MStreamingPlaylist & | ||
20 | Use<'VideoFiles', MVideoFile[]> & | 23 | Use<'VideoFiles', MVideoFile[]> & |
21 | Use<'Video', MVideo> | 24 | Use<'Video', MVideo> |
22 | 25 | ||
23 | export type MStreamingPlaylistRedundancies = MStreamingPlaylist & | 26 | export type MStreamingPlaylistRedundanciesAll = |
27 | MStreamingPlaylist & | ||
28 | Use<'VideoFiles', MVideoFile[]> & | ||
29 | Use<'RedundancyVideos', MVideoRedundancy[]> | ||
30 | |||
31 | export type MStreamingPlaylistRedundancies = | ||
32 | MStreamingPlaylist & | ||
24 | Use<'VideoFiles', MVideoFile[]> & | 33 | Use<'VideoFiles', MVideoFile[]> & |
25 | Use<'RedundancyVideos', MVideoRedundancyFileUrl[]> | 34 | Use<'RedundancyVideos', MVideoRedundancyFileUrl[]> |
26 | 35 | ||
27 | export type MStreamingPlaylistRedundanciesOpt = MStreamingPlaylist & | 36 | export type MStreamingPlaylistRedundanciesOpt = |
37 | MStreamingPlaylist & | ||
28 | Use<'VideoFiles', MVideoFile[]> & | 38 | Use<'VideoFiles', MVideoFile[]> & |
29 | PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> | 39 | PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> |
30 | 40 | ||
diff --git a/server/typings/models/video/video.ts b/server/typings/models/video/video.ts index 7f69a91de..022a9566d 100644 --- a/server/typings/models/video/video.ts +++ b/server/typings/models/video/video.ts | |||
@@ -9,9 +9,14 @@ import { | |||
9 | MChannelUserId | 9 | MChannelUserId |
10 | } from './video-channels' | 10 | } from './video-channels' |
11 | import { MTag } from './tag' | 11 | import { MTag } from './tag' |
12 | import { MVideoCaptionLanguage } from './video-caption' | 12 | import { MVideoCaptionLanguage, MVideoCaptionLanguageUrl } from './video-caption' |
13 | import { MStreamingPlaylistFiles, MStreamingPlaylistRedundancies, MStreamingPlaylistRedundanciesOpt } from './video-streaming-playlist' | 13 | import { |
14 | import { MVideoFile, MVideoFileRedundanciesOpt } from './video-file' | 14 | MStreamingPlaylistFiles, |
15 | MStreamingPlaylistRedundancies, | ||
16 | MStreamingPlaylistRedundanciesAll, | ||
17 | MStreamingPlaylistRedundanciesOpt | ||
18 | } from './video-streaming-playlist' | ||
19 | import { MVideoFile, MVideoFileRedundanciesAll, MVideoFileRedundanciesOpt } from './video-file' | ||
15 | import { MThumbnail } from './thumbnail' | 20 | import { MThumbnail } from './thumbnail' |
16 | import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist' | 21 | import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist' |
17 | import { MScheduleVideoUpdate } from './schedule-video-update' | 22 | import { MScheduleVideoUpdate } from './schedule-video-update' |
@@ -21,7 +26,8 @@ type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M> | |||
21 | 26 | ||
22 | // ############################################################################ | 27 | // ############################################################################ |
23 | 28 | ||
24 | export type MVideo = Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' | | 29 | export type MVideo = |
30 | Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' | | ||
25 | 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' | | 31 | 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' | |
26 | 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'> | 32 | 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'> |
27 | 33 | ||
@@ -31,6 +37,7 @@ export type MVideoId = Pick<MVideo, 'id'> | |||
31 | export type MVideoUrl = Pick<MVideo, 'url'> | 37 | export type MVideoUrl = Pick<MVideo, 'url'> |
32 | export type MVideoUUID = Pick<MVideo, 'uuid'> | 38 | export type MVideoUUID = Pick<MVideo, 'uuid'> |
33 | 39 | ||
40 | export type MVideoImmutable = Pick<MVideo, 'id' | 'url' | 'uuid' | 'remote' | 'isOwned'> | ||
34 | export type MVideoIdUrl = MVideoId & MVideoUrl | 41 | export type MVideoIdUrl = MVideoId & MVideoUrl |
35 | export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'> | 42 | export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'> |
36 | 43 | ||
@@ -39,50 +46,63 @@ export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'> | |||
39 | // Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists | 46 | // Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists |
40 | 47 | ||
41 | // "With" to not confuse with the VideoFile model | 48 | // "With" to not confuse with the VideoFile model |
42 | export type MVideoWithFile = MVideo & | 49 | export type MVideoWithFile = |
50 | MVideo & | ||
43 | Use<'VideoFiles', MVideoFile[]> & | 51 | Use<'VideoFiles', MVideoFile[]> & |
44 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> | 52 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> |
45 | 53 | ||
46 | export type MVideoThumbnail = MVideo & | 54 | export type MVideoThumbnail = |
55 | MVideo & | ||
47 | Use<'Thumbnails', MThumbnail[]> | 56 | Use<'Thumbnails', MThumbnail[]> |
48 | 57 | ||
49 | export type MVideoIdThumbnail = MVideoId & | 58 | export type MVideoIdThumbnail = |
59 | MVideoId & | ||
50 | Use<'Thumbnails', MThumbnail[]> | 60 | Use<'Thumbnails', MThumbnail[]> |
51 | 61 | ||
52 | export type MVideoWithFileThumbnail = MVideo & | 62 | export type MVideoWithFileThumbnail = |
63 | MVideo & | ||
53 | Use<'VideoFiles', MVideoFile[]> & | 64 | Use<'VideoFiles', MVideoFile[]> & |
54 | Use<'Thumbnails', MThumbnail[]> | 65 | Use<'Thumbnails', MThumbnail[]> |
55 | 66 | ||
56 | export type MVideoThumbnailBlacklist = MVideo & | 67 | export type MVideoThumbnailBlacklist = |
68 | MVideo & | ||
57 | Use<'Thumbnails', MThumbnail[]> & | 69 | Use<'Thumbnails', MThumbnail[]> & |
58 | Use<'VideoBlacklist', MVideoBlacklistLight> | 70 | Use<'VideoBlacklist', MVideoBlacklistLight> |
59 | 71 | ||
60 | export type MVideoTag = MVideo & | 72 | export type MVideoTag = |
73 | MVideo & | ||
61 | Use<'Tags', MTag[]> | 74 | Use<'Tags', MTag[]> |
62 | 75 | ||
63 | export type MVideoWithSchedule = MVideo & | 76 | export type MVideoWithSchedule = |
77 | MVideo & | ||
64 | PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate> | 78 | PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate> |
65 | 79 | ||
66 | export type MVideoWithCaptions = MVideo & | 80 | export type MVideoWithCaptions = |
81 | MVideo & | ||
67 | Use<'VideoCaptions', MVideoCaptionLanguage[]> | 82 | Use<'VideoCaptions', MVideoCaptionLanguage[]> |
68 | 83 | ||
69 | export type MVideoWithStreamingPlaylist = MVideo & | 84 | export type MVideoWithStreamingPlaylist = |
85 | MVideo & | ||
70 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> | 86 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> |
71 | 87 | ||
72 | // ############################################################################ | 88 | // ############################################################################ |
73 | 89 | ||
74 | // Associations with not all their attributes | 90 | // Associations with not all their attributes |
75 | 91 | ||
76 | export type MVideoUserHistory = MVideo & | 92 | export type MVideoUserHistory = |
93 | MVideo & | ||
77 | Use<'UserVideoHistories', MUserVideoHistoryTime[]> | 94 | Use<'UserVideoHistories', MUserVideoHistoryTime[]> |
78 | 95 | ||
79 | export type MVideoWithBlacklistLight = MVideo & | 96 | export type MVideoWithBlacklistLight = |
97 | MVideo & | ||
80 | Use<'VideoBlacklist', MVideoBlacklistLight> | 98 | Use<'VideoBlacklist', MVideoBlacklistLight> |
81 | 99 | ||
82 | export type MVideoAccountLight = MVideo & | 100 | export type MVideoAccountLight = |
101 | MVideo & | ||
83 | Use<'VideoChannel', MChannelAccountLight> | 102 | Use<'VideoChannel', MChannelAccountLight> |
84 | 103 | ||
85 | export type MVideoWithRights = MVideo & | 104 | export type MVideoWithRights = |
105 | MVideo & | ||
86 | Use<'VideoBlacklist', MVideoBlacklistLight> & | 106 | Use<'VideoBlacklist', MVideoBlacklistLight> & |
87 | Use<'Thumbnails', MThumbnail[]> & | 107 | Use<'Thumbnails', MThumbnail[]> & |
88 | Use<'VideoChannel', MChannelUserId> | 108 | Use<'VideoChannel', MChannelUserId> |
@@ -91,12 +111,14 @@ export type MVideoWithRights = MVideo & | |||
91 | 111 | ||
92 | // All files with some additional associations | 112 | // All files with some additional associations |
93 | 113 | ||
94 | export type MVideoWithAllFiles = MVideo & | 114 | export type MVideoWithAllFiles = |
115 | MVideo & | ||
95 | Use<'VideoFiles', MVideoFile[]> & | 116 | Use<'VideoFiles', MVideoFile[]> & |
96 | Use<'Thumbnails', MThumbnail[]> & | 117 | Use<'Thumbnails', MThumbnail[]> & |
97 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> | 118 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> |
98 | 119 | ||
99 | export type MVideoAccountLightBlacklistAllFiles = MVideo & | 120 | export type MVideoAccountLightBlacklistAllFiles = |
121 | MVideo & | ||
100 | Use<'VideoFiles', MVideoFile[]> & | 122 | Use<'VideoFiles', MVideoFile[]> & |
101 | Use<'Thumbnails', MThumbnail[]> & | 123 | Use<'Thumbnails', MThumbnail[]> & |
102 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> & | 124 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> & |
@@ -107,17 +129,21 @@ export type MVideoAccountLightBlacklistAllFiles = MVideo & | |||
107 | 129 | ||
108 | // With account | 130 | // With account |
109 | 131 | ||
110 | export type MVideoAccountDefault = MVideo & | 132 | export type MVideoAccountDefault = |
133 | MVideo & | ||
111 | Use<'VideoChannel', MChannelAccountDefault> | 134 | Use<'VideoChannel', MChannelAccountDefault> |
112 | 135 | ||
113 | export type MVideoThumbnailAccountDefault = MVideo & | 136 | export type MVideoThumbnailAccountDefault = |
137 | MVideo & | ||
114 | Use<'Thumbnails', MThumbnail[]> & | 138 | Use<'Thumbnails', MThumbnail[]> & |
115 | Use<'VideoChannel', MChannelAccountDefault> | 139 | Use<'VideoChannel', MChannelAccountDefault> |
116 | 140 | ||
117 | export type MVideoWithChannelActor = MVideo & | 141 | export type MVideoWithChannelActor = |
142 | MVideo & | ||
118 | Use<'VideoChannel', MChannelActor> | 143 | Use<'VideoChannel', MChannelActor> |
119 | 144 | ||
120 | export type MVideoFullLight = MVideo & | 145 | export type MVideoFullLight = |
146 | MVideo & | ||
121 | Use<'Thumbnails', MThumbnail[]> & | 147 | Use<'Thumbnails', MThumbnail[]> & |
122 | Use<'VideoBlacklist', MVideoBlacklistLight> & | 148 | Use<'VideoBlacklist', MVideoBlacklistLight> & |
123 | Use<'Tags', MTag[]> & | 149 | Use<'Tags', MTag[]> & |
@@ -131,18 +157,20 @@ export type MVideoFullLight = MVideo & | |||
131 | 157 | ||
132 | // API | 158 | // API |
133 | 159 | ||
134 | export type MVideoAP = MVideo & | 160 | export type MVideoAP = |
161 | MVideo & | ||
135 | Use<'Tags', MTag[]> & | 162 | Use<'Tags', MTag[]> & |
136 | Use<'VideoChannel', MChannelAccountLight> & | 163 | Use<'VideoChannel', MChannelAccountLight> & |
137 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> & | 164 | Use<'VideoStreamingPlaylists', MStreamingPlaylistFiles[]> & |
138 | Use<'VideoCaptions', MVideoCaptionLanguage[]> & | 165 | Use<'VideoCaptions', MVideoCaptionLanguageUrl[]> & |
139 | Use<'VideoBlacklist', MVideoBlacklistUnfederated> & | 166 | Use<'VideoBlacklist', MVideoBlacklistUnfederated> & |
140 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> & | 167 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> & |
141 | Use<'Thumbnails', MThumbnail[]> | 168 | Use<'Thumbnails', MThumbnail[]> |
142 | 169 | ||
143 | export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'> | 170 | export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'> |
144 | 171 | ||
145 | export type MVideoDetails = MVideo & | 172 | export type MVideoDetails = |
173 | MVideo & | ||
146 | Use<'VideoBlacklist', MVideoBlacklistLight> & | 174 | Use<'VideoBlacklist', MVideoBlacklistLight> & |
147 | Use<'Tags', MTag[]> & | 175 | Use<'Tags', MTag[]> & |
148 | Use<'VideoChannel', MChannelAccountLight> & | 176 | Use<'VideoChannel', MChannelAccountLight> & |
@@ -152,23 +180,31 @@ export type MVideoDetails = MVideo & | |||
152 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> & | 180 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> & |
153 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> | 181 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> |
154 | 182 | ||
155 | export type MVideoForUser = MVideo & | 183 | export type MVideoForUser = |
184 | MVideo & | ||
156 | Use<'VideoChannel', MChannelAccountDefault> & | 185 | Use<'VideoChannel', MChannelAccountDefault> & |
157 | Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & | 186 | Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & |
158 | Use<'VideoBlacklist', MVideoBlacklistLight> & | 187 | Use<'VideoBlacklist', MVideoBlacklistLight> & |
159 | Use<'Thumbnails', MThumbnail[]> | 188 | Use<'Thumbnails', MThumbnail[]> |
160 | 189 | ||
190 | export type MVideoForRedundancyAPI = | ||
191 | MVideo & | ||
192 | Use<'VideoFiles', MVideoFileRedundanciesAll[]> & | ||
193 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesAll[]> | ||
194 | |||
161 | // ############################################################################ | 195 | // ############################################################################ |
162 | 196 | ||
163 | // Format for API or AP object | 197 | // Format for API or AP object |
164 | 198 | ||
165 | export type MVideoFormattable = MVideo & | 199 | export type MVideoFormattable = |
200 | MVideo & | ||
166 | PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> & | 201 | PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> & |
167 | Use<'VideoChannel', MChannelAccountSummaryFormattable> & | 202 | Use<'VideoChannel', MChannelAccountSummaryFormattable> & |
168 | PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> & | 203 | PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> & |
169 | PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>> | 204 | PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>> |
170 | 205 | ||
171 | export type MVideoFormattableDetails = MVideoFormattable & | 206 | export type MVideoFormattableDetails = |
207 | MVideoFormattable & | ||
172 | Use<'VideoChannel', MChannelFormattable> & | 208 | Use<'VideoChannel', MChannelFormattable> & |
173 | Use<'Tags', MTag[]> & | 209 | Use<'Tags', MTag[]> & |
174 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> & | 210 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> & |
diff --git a/server/typings/plugins/register-server-option.model.ts b/server/typings/plugins/register-server-option.model.ts index 54753cc01..8f1d66007 100644 --- a/server/typings/plugins/register-server-option.model.ts +++ b/server/typings/plugins/register-server-option.model.ts | |||
@@ -1,11 +1,55 @@ | |||
1 | import { logger } from '../../helpers/logger' | 1 | import * as Bluebird from 'bluebird' |
2 | import { Router } from 'express' | ||
3 | import { Logger } from 'winston' | ||
4 | import { ActorModel } from '@server/models/activitypub/actor' | ||
5 | import { VideoBlacklistCreate } from '@shared/models' | ||
6 | import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model' | ||
7 | import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model' | ||
8 | import { | ||
9 | RegisterServerAuthExternalOptions, | ||
10 | RegisterServerAuthExternalResult, | ||
11 | RegisterServerAuthPassOptions | ||
12 | } from '@shared/models/plugins/register-server-auth.model' | ||
2 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' | 13 | import { PluginSettingsManager } from '../../../shared/models/plugins/plugin-settings-manager.model' |
3 | import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' | 14 | import { PluginStorageManager } from '../../../shared/models/plugins/plugin-storage-manager.model' |
4 | import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' | ||
5 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | ||
6 | import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model' | 15 | import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model' |
7 | import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model' | 16 | import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model' |
8 | import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model' | 17 | import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model' |
18 | import { RegisterServerHookOptions } from '../../../shared/models/plugins/register-server-hook.model' | ||
19 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | ||
20 | import { MVideoThumbnail } from '../models' | ||
21 | |||
22 | export type PeerTubeHelpers = { | ||
23 | logger: Logger | ||
24 | |||
25 | database: { | ||
26 | query: Function | ||
27 | } | ||
28 | |||
29 | videos: { | ||
30 | loadByUrl: (url: string) => Bluebird<MVideoThumbnail> | ||
31 | |||
32 | removeVideo: (videoId: number) => Promise<void> | ||
33 | } | ||
34 | |||
35 | config: { | ||
36 | getWebserverUrl: () => string | ||
37 | } | ||
38 | |||
39 | moderation: { | ||
40 | blockServer: (options: { byAccountId: number, hostToBlock: string }) => Promise<void> | ||
41 | unblockServer: (options: { byAccountId: number, hostToUnblock: string }) => Promise<void> | ||
42 | blockAccount: (options: { byAccountId: number, handleToBlock: string }) => Promise<void> | ||
43 | unblockAccount: (options: { byAccountId: number, handleToUnblock: string }) => Promise<void> | ||
44 | |||
45 | blacklistVideo: (options: { videoIdOrUUID: number | string, createOptions: VideoBlacklistCreate }) => Promise<void> | ||
46 | unblacklistVideo: (options: { videoIdOrUUID: number | string }) => Promise<void> | ||
47 | } | ||
48 | |||
49 | server: { | ||
50 | getServerActor: () => Promise<ActorModel> | ||
51 | } | ||
52 | } | ||
9 | 53 | ||
10 | export type RegisterServerOptions = { | 54 | export type RegisterServerOptions = { |
11 | registerHook: (options: RegisterServerHookOptions) => void | 55 | registerHook: (options: RegisterServerHookOptions) => void |
@@ -20,7 +64,19 @@ export type RegisterServerOptions = { | |||
20 | videoLanguageManager: PluginVideoLanguageManager | 64 | videoLanguageManager: PluginVideoLanguageManager |
21 | videoLicenceManager: PluginVideoLicenceManager | 65 | videoLicenceManager: PluginVideoLicenceManager |
22 | 66 | ||
23 | peertubeHelpers: { | 67 | videoPrivacyManager: PluginVideoPrivacyManager |
24 | logger: typeof logger | 68 | playlistPrivacyManager: PluginPlaylistPrivacyManager |
25 | } | 69 | |
70 | registerIdAndPassAuth: (options: RegisterServerAuthPassOptions) => void | ||
71 | registerExternalAuth: (options: RegisterServerAuthExternalOptions) => RegisterServerAuthExternalResult | ||
72 | unregisterIdAndPassAuth: (authName: string) => void | ||
73 | unregisterExternalAuth: (authName: string) => void | ||
74 | |||
75 | // Get plugin router to create custom routes | ||
76 | // Base routes of this router are | ||
77 | // * /plugins/:pluginName/:pluginVersion/router/... | ||
78 | // * /plugins/:pluginName/router/... | ||
79 | getRouter(): Router | ||
80 | |||
81 | peertubeHelpers: PeerTubeHelpers | ||
26 | } | 82 | } |
diff --git a/server/typings/utils.ts b/server/typings/utils.ts index 24d43b258..55500d8c4 100644 --- a/server/typings/utils.ts +++ b/server/typings/utils.ts | |||
@@ -1,3 +1,5 @@ | |||
1 | /* eslint-disable @typescript-eslint/array-type */ | ||
2 | |||
1 | export type FunctionPropertyNames<T> = { | 3 | export type FunctionPropertyNames<T> = { |
2 | [K in keyof T]: T[K] extends Function ? K : never | 4 | [K in keyof T]: T[K] extends Function ? K : never |
3 | }[keyof T] | 5 | }[keyof T] |