aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/activitypub/client.ts57
-rw-r--r--server/controllers/activitypub/inbox.ts10
-rw-r--r--server/controllers/api/accounts.ts19
-rw-r--r--server/controllers/api/config.ts91
-rw-r--r--server/controllers/api/index.ts4
-rw-r--r--server/controllers/api/jobs.ts2
-rw-r--r--server/controllers/api/overviews.ts72
-rw-r--r--server/controllers/api/plugins.ts2
-rw-r--r--server/controllers/api/search.ts8
-rw-r--r--server/controllers/api/server/debug.ts6
-rw-r--r--server/controllers/api/server/follows.ts8
-rw-r--r--server/controllers/api/server/logs.ts10
-rw-r--r--server/controllers/api/server/redundancy.ts84
-rw-r--r--server/controllers/api/server/server-blocklist.ts19
-rw-r--r--server/controllers/api/server/stats.ts19
-rw-r--r--server/controllers/api/users/index.ts43
-rw-r--r--server/controllers/api/users/me.ts4
-rw-r--r--server/controllers/api/users/my-blocklist.ts16
-rw-r--r--server/controllers/api/users/my-history.ts2
-rw-r--r--server/controllers/api/users/my-subscriptions.ts7
-rw-r--r--server/controllers/api/users/token.ts37
-rw-r--r--server/controllers/api/video-channel.ts8
-rw-r--r--server/controllers/api/video-playlist.ts12
-rw-r--r--server/controllers/api/videos/abuse.ts37
-rw-r--r--server/controllers/api/videos/blacklist.ts68
-rw-r--r--server/controllers/api/videos/captions.ts4
-rw-r--r--server/controllers/api/videos/comment.ts2
-rw-r--r--server/controllers/api/videos/import.ts107
-rw-r--r--server/controllers/api/videos/index.ts55
-rw-r--r--server/controllers/api/videos/ownership.ts4
-rw-r--r--server/controllers/api/videos/rate.ts2
-rw-r--r--server/controllers/bots.ts14
-rw-r--r--server/controllers/client.ts4
-rw-r--r--server/controllers/feeds.ts4
-rw-r--r--server/controllers/plugins.ts59
-rw-r--r--server/controllers/static.ts33
-rw-r--r--server/controllers/tracker.ts15
-rw-r--r--server/controllers/webfinger.ts7
38 files changed, 630 insertions, 325 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
2import * as express from 'express' 1import * as express from 'express'
2import * as cors from 'cors'
3import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' 3import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos'
4import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' 4import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub'
5import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants' 5import { 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'
17import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' 17import { getAccountVideoRateValidatorFactory, videoCommentGetValidator } from '../../middlewares/validators'
18import { AccountModel } from '../../models/account/account' 18import { AccountModel } from '../../models/account/account'
19import { ActorFollowModel } from '../../models/activitypub/actor-follow' 19import { ActorFollowModel } from '../../models/activitypub/actor-follow'
20import { VideoModel } from '../../models/video/video' 20import { VideoModel } from '../../models/video/video'
@@ -24,22 +24,25 @@ import { cacheRoute } from '../../middlewares/cache'
24import { activityPubResponse } from './utils' 24import { activityPubResponse } from './utils'
25import { AccountVideoRateModel } from '../../models/account/account-video-rate' 25import { AccountVideoRateModel } from '../../models/account/account-video-rate'
26import { 26import {
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'
33import { VideoCaptionModel } from '../../models/video/video-caption' 32import { VideoCaptionModel } from '../../models/video/video-caption'
34import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy' 33import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy'
35import { getServerActor } from '../../helpers/utils'
36import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' 34import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike'
37import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' 35import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
38import { VideoPlaylistModel } from '../../models/video/video-playlist' 36import { VideoPlaylistModel } from '../../models/video/video-playlist'
39import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' 37import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
40import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models' 38import { MAccountId, MActorId, MVideoAPWithoutCaption, MVideoId } from '@server/typings/models'
39import { getServerActor } from '@server/models/application/application'
40import { getRateUrl } from '@server/lib/activitypub/video-rates'
41 41
42const activityPubClientRouter = express.Router() 42const activityPubClientRouter = express.Router()
43activityPubClientRouter.use(cors())
44
45// Intercept ActivityPub client requests
43 46
44activityPubClientRouter.get('/accounts?/:name', 47activityPubClientRouter.get('/accounts?/:name',
45 executeIfActivityPub, 48 executeIfActivityPub,
@@ -63,13 +66,13 @@ activityPubClientRouter.get('/accounts?/:name/playlists',
63) 66)
64activityPubClientRouter.get('/accounts?/:name/likes/:videoId', 67activityPubClientRouter.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)
69activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', 72activityPubClientRouter.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
75activityPubClientRouter.get('/videos/watch/:id', 78activityPubClientRouter.get('/videos/watch/:id',
@@ -85,7 +88,7 @@ activityPubClientRouter.get('/videos/watch/:id/activity',
85) 88)
86activityPubClientRouter.get('/videos/watch/:id/announces', 89activityPubClientRouter.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)
91activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', 94activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
@@ -95,17 +98,17 @@ activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
95) 98)
96activityPubClientRouter.get('/videos/watch/:id/likes', 99activityPubClientRouter.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)
101activityPubClientRouter.get('/videos/watch/:id/dislikes', 104activityPubClientRouter.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)
106activityPubClientRouter.get('/videos/watch/:id/comments', 109activityPubClientRouter.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)
111activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', 114activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
@@ -122,7 +125,7 @@ activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity
122activityPubClientRouter.get('/video-channels/:name', 125activityPubClientRouter.get('/video-channels/:name',
123 executeIfActivityPub, 126 executeIfActivityPub,
124 asyncMiddleware(localVideoChannelValidator), 127 asyncMiddleware(localVideoChannelValidator),
125 asyncMiddleware(videoChannelController) 128 videoChannelController
126) 129)
127activityPubClientRouter.get('/video-channels/:name/followers', 130activityPubClientRouter.get('/video-channels/:name/followers',
128 executeIfActivityPub, 131 executeIfActivityPub,
@@ -154,7 +157,7 @@ activityPubClientRouter.get('/video-playlists/:playlistId',
154activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', 157activityPubClientRouter.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
195function getAccountVideoRate (rateType: VideoRateType) { 198function 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
240async function videoAnnouncesController (req: express.Request, res: express.Response) { 243async 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
255async function videoLikesController (req: express.Request, res: express.Response) { 258async 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
262async function videoDislikesController (req: express.Request, res: express.Response) { 265async 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
269async function videoCommentsController (req: express.Request, res: express.Response) { 272async 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
284async function videoChannelController (req: express.Request, res: express.Response) { 287function 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
343async function videoPlaylistController (req: express.Request, res: express.Response) { 346async 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
356async function videoPlaylistElementController (req: express.Request, res: express.Response) { 359function 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
389function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) { 392function 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
51function inboxController (req: express.Request, res: express.Response) { 55function 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects, getServerActor } from '../../helpers/utils' 2import { getFormattedObjects } from '../../helpers/utils'
3import { 3import {
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'
22import { AccountModel } from '../../models/account/account' 23import { AccountModel } from '../../models/account/account'
23import { AccountVideoRateModel } from '../../models/account/account-video-rate' 24import { AccountVideoRateModel } from '../../models/account/account-video-rate'
24import { VideoModel } from '../../models/video/video' 25import { VideoModel } from '../../models/video/video'
25import { buildNSFWFilter, isUserAbleToSearchRemoteURI, getCountVideos } from '../../helpers/express-utils' 26import { buildNSFWFilter, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
26import { VideoChannelModel } from '../../models/video/video-channel' 27import { VideoChannelModel } from '../../models/video/video-channel'
27import { JobQueue } from '../../lib/job-queue' 28import { JobQueue } from '../../lib/job-queue'
28import { logger } from '../../helpers/logger'
29import { VideoPlaylistModel } from '../../models/video/video-playlist' 29import { VideoPlaylistModel } from '../../models/video/video-playlist'
30import { 30import { commonVideoPlaylistFiltersValidator, videoPlaylistsSearchValidator } from '../../middlewares/validators/videos/video-playlists'
31 commonVideoPlaylistFiltersValidator, 31import { getServerActor } from '@server/models/application/application'
32 videoPlaylistsSearchValidator
33} from '../../middlewares/validators/videos/video-playlists'
34 32
35const accountsRouter = express.Router() 33const accountsRouter = express.Router()
36 34
@@ -60,6 +58,7 @@ accountsRouter.get('/:accountName/videos',
60 58
61accountsRouter.get('/:accountName/video-channels', 59accountsRouter.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 @@
1import { Hooks } from '@server/lib/plugins/hooks'
1import * as express from 'express' 2import * as express from 'express'
3import { remove, writeJSON } from 'fs-extra'
2import { snakeCase } from 'lodash' 4import { snakeCase } from 'lodash'
3import { ServerConfig, UserRight } from '../../../shared' 5import validator from 'validator'
6import { RegisteredExternalAuthConfig, RegisteredIdAndPassAuthConfig, ServerConfig, UserRight } from '../../../shared'
4import { About } from '../../../shared/models/server/about.model' 7import { About } from '../../../shared/models/server/about.model'
5import { CustomConfig } from '../../../shared/models/server/custom-config.model' 8import { CustomConfig } from '../../../shared/models/server/custom-config.model'
9import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
10import { objectConverter } from '../../helpers/core-utils'
6import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' 11import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
12import { getServerCommit } from '../../helpers/utils'
13import { CONFIG, isEmailEnabled, reloadConfig } from '../../initializers/config'
7import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants' 14import { CONSTRAINTS_FIELDS, DEFAULT_THEME_NAME, PEERTUBE_VERSION } from '../../initializers/constants'
8import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
9import { customConfigUpdateValidator } from '../../middlewares/validators/config'
10import { ClientHtml } from '../../lib/client-html' 15import { ClientHtml } from '../../lib/client-html'
11import { auditLoggerFactory, CustomConfigAuditView, getAuditIdFromRes } from '../../helpers/audit-logger'
12import { remove, writeJSON } from 'fs-extra'
13import { getServerCommit } from '../../helpers/utils'
14import { Emailer } from '../../lib/emailer'
15import validator from 'validator'
16import { objectConverter } from '../../helpers/core-utils'
17import { CONFIG, reloadConfig } from '../../initializers/config'
18import { PluginManager } from '../../lib/plugins/plugin-manager' 16import { PluginManager } from '../../lib/plugins/plugin-manager'
19import { getThemeOrDefault } from '../../lib/plugins/theme-utils' 17import { getThemeOrDefault } from '../../lib/plugins/theme-utils'
20import { Hooks } from '@server/lib/plugins/hooks' 18import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
19import { customConfigUpdateValidator } from '../../middlewares/validators/config'
21 20
22const configRouter = express.Router() 21const configRouter = express.Router()
23 22
@@ -31,12 +30,12 @@ configRouter.get('/',
31configRouter.get('/custom', 30configRouter.get('/custom',
32 authenticate, 31 authenticate,
33 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION), 32 ensureUserHasRight(UserRight.MANAGE_CONFIGURATION),
34 asyncMiddleware(getCustomConfig) 33 getCustomConfig
35) 34)
36configRouter.put('/custom', 35configRouter.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)
42configRouter.delete('/custom', 41configRouter.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
199async function getCustomConfig (req: express.Request, res: express.Response) { 206function 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
251function getEnabledResolutions () { 258function 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
274function 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
292function 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
269export { 312export {
@@ -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 @@
1import * as express from 'express' 1import * as express from 'express'
2import * as RateLimit from 'express-rate-limit'
3import { configRouter } from './config' 2import { configRouter } from './config'
4import { jobsRouter } from './jobs' 3import { jobsRouter } from './jobs'
5import { oauthClientsRouter } from './oauth-clients' 4import { oauthClientsRouter } from './oauth-clients'
@@ -15,6 +14,7 @@ import { overviewsRouter } from './overviews'
15import { videoPlaylistRouter } from './video-playlist' 14import { videoPlaylistRouter } from './video-playlist'
16import { CONFIG } from '../../initializers/config' 15import { CONFIG } from '../../initializers/config'
17import { pluginRouter } from './plugins' 16import { pluginRouter } from './plugins'
17import * as RateLimit from 'express-rate-limit'
18 18
19const apiRouter = express.Router() 19const 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
29const apiRateLimiter = RateLimit({ 27const 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { buildNSFWFilter } from '../../helpers/express-utils' 2import { buildNSFWFilter } from '../../helpers/express-utils'
3import { VideoModel } from '../../models/video/video' 3import { VideoModel } from '../../models/video/video'
4import { asyncMiddleware } from '../../middlewares' 4import { asyncMiddleware, optionalAuthenticate, videosOverviewValidator } from '../../middlewares'
5import { TagModel } from '../../models/video/tag' 5import { TagModel } from '../../models/video/tag'
6import { VideosOverview } from '../../../shared/models/overviews' 6import { CategoryOverview, ChannelOverview, TagOverview, VideosOverview } from '../../../shared/models/overviews'
7import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers/constants' 7import { MEMOIZE_TTL, OVERVIEWS } from '../../initializers/constants'
8import { cacheRoute } from '../../middlewares/cache'
9import * as memoizee from 'memoizee' 8import * as memoizee from 'memoizee'
9import { logger } from '@server/helpers/logger'
10 10
11const overviewsRouter = express.Router() 11const overviewsRouter = express.Router()
12 12
13overviewsRouter.get('/videos', 13overviewsRouter.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 }
24const buildSamples = memoizee(async function () { 25const 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
35async function getVideosOverview (req: express.Request, res: express.Response) { 40async 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
58async function getVideosByTag (tag: string, res: express.Response) { 65async 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
69async function getVideosByCategory (category: number, res: express.Response) { 79async 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
80async function getVideosByChannel (channelId: number, res: express.Response) { 93async 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
91async function getVideos ( 107async 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 2import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
3import { getFormattedObjects, getServerActor } from '../../helpers/utils' 3import { getFormattedObjects } from '../../helpers/utils'
4import { VideoModel } from '../../models/video/video' 4import { VideoModel } from '../../models/video/video'
5import { 5import {
6 asyncMiddleware, 6 asyncMiddleware,
@@ -15,11 +15,13 @@ import {
15 videosSearchValidator 15 videosSearchValidator
16} from '../../middlewares' 16} from '../../middlewares'
17import { VideoChannelsSearchQuery, VideosSearchQuery } from '../../../shared/models/search' 17import { VideoChannelsSearchQuery, VideosSearchQuery } from '../../../shared/models/search'
18import { getOrCreateActorAndServerAndModel, getOrCreateVideoAndAccountAndChannel } from '../../lib/activitypub' 18import { getOrCreateActorAndServerAndModel } from '../../lib/activitypub/actor'
19import { logger } from '../../helpers/logger' 19import { logger } from '../../helpers/logger'
20import { VideoChannelModel } from '../../models/video/video-channel' 20import { VideoChannelModel } from '../../models/video/video-channel'
21import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' 21import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger'
22import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models' 22import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models'
23import { getServerActor } from '@server/models/application/application'
24import { getOrCreateVideoAndAccountAndChannel } from '@server/lib/activitypub/videos'
23 25
24const searchRouter = express.Router() 26const 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import { authenticate, ensureUserHasRight } from '../../../middlewares'
4 4
5const debugRouter = express.Router() 5const debugRouter = express.Router()
6 6
7debugRouter.get('/debug', 7debugRouter.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
21async function getDebug (req: express.Request, res: express.Response) { 21function 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { SERVER_ACTOR_NAME } from '../../../initializers/constants' 5import { SERVER_ACTOR_NAME } from '../../../initializers/constants'
6import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' 6import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send'
7import { 7import {
@@ -24,9 +24,10 @@ import {
24} from '../../../middlewares/validators' 24} from '../../../middlewares/validators'
25import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 25import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
26import { JobQueue } from '../../../lib/job-queue' 26import { JobQueue } from '../../../lib/job-queue'
27import { removeRedundancyOf } from '../../../lib/redundancy' 27import { removeRedundanciesOfServer } from '../../../lib/redundancy'
28import { sequelizeTypescript } from '../../../initializers/database' 28import { sequelizeTypescript } from '../../../initializers/database'
29import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' 29import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow'
30import { getServerActor } from '@server/models/application/application'
30 31
31const serverFollowsRouter = express.Router() 32const serverFollowsRouter = express.Router()
32serverFollowsRouter.get('/following', 33serverFollowsRouter.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
61async function generateOutput (options: { 61async 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import {
4import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy' 4 asyncMiddleware,
5import { removeRedundancyOf } from '../../../lib/redundancy' 5 authenticate,
6 ensureUserHasRight,
7 paginationValidator,
8 setDefaultPagination,
9 setDefaultVideoRedundanciesSort,
10 videoRedundanciesSortValidator
11} from '../../../middlewares'
12import {
13 listVideoRedundanciesValidator,
14 updateServerRedundancyValidator,
15 addVideoRedundancyValidator,
16 removeVideoRedundancyValidator
17} from '../../../middlewares/validators/redundancy'
18import { removeRedundanciesOfServer, removeVideoRedundancy } from '../../../lib/redundancy'
6import { logger } from '../../../helpers/logger' 19import { logger } from '../../../helpers/logger'
20import { VideoRedundancyModel } from '@server/models/redundancy/video-redundancy'
21import { JobQueue } from '@server/lib/job-queue'
7 22
8const serverRedundancyRouter = express.Router() 23const serverRedundancyRouter = express.Router()
9 24
@@ -14,6 +29,31 @@ serverRedundancyRouter.put('/redundancy/:host',
14 asyncMiddleware(updateRedundancy) 29 asyncMiddleware(updateRedundancy)
15) 30)
16 31
32serverRedundancyRouter.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
43serverRedundancyRouter.post('/redundancy/videos',
44 authenticate,
45 ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES),
46 addVideoRedundancyValidator,
47 asyncMiddleware(addVideoRedundancy)
48)
49
50serverRedundancyRouter.delete('/redundancy/videos/:redundancyId',
51 authenticate,
52 ensureUserHasRight(UserRight.MANAGE_VIDEOS_REDUNDANCIES),
53 removeVideoRedundancyValidator,
54 asyncMiddleware(removeVideoRedundancyController)
55)
56
17// --------------------------------------------------------------------------- 57// ---------------------------------------------------------------------------
18 58
19export { 59export {
@@ -22,6 +62,42 @@ export {
22 62
23// --------------------------------------------------------------------------- 63// ---------------------------------------------------------------------------
24 64
65async 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
82async 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
95async function removeVideoRedundancyController (req: express.Request, res: express.Response) {
96 await removeVideoRedundancy(res.locals.videoRedundancy)
97
98 return res.sendStatus(204)
99}
100
25async function updateRedundancy (req: express.Request, res: express.Response) { 101async 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'multer' 2import 'multer'
3import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 3import { getFormattedObjects } from '../../../helpers/utils'
4import { 4import {
5 asyncMiddleware, 5 asyncMiddleware,
6 asyncRetryTransactionMiddleware, 6 asyncRetryTransactionMiddleware,
@@ -22,6 +22,7 @@ import { AccountBlocklistModel } from '../../../models/account/account-blocklist
22import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' 22import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
23import { ServerBlocklistModel } from '../../../models/server/server-blocklist' 23import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
24import { UserRight } from '../../../../shared/models/users' 24import { UserRight } from '../../../../shared/models/users'
25import { getServerActor } from '@server/models/application/application'
25 26
26const serverBlocklistRouter = express.Router() 27const serverBlocklistRouter = express.Router()
27 28
@@ -82,7 +83,13 @@ export {
82async function listBlockedAccounts (req: express.Request, res: express.Response) { 83async 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) {
107async function listBlockedServers (req: express.Request, res: express.Response) { 114async 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'
10import { cacheRoute } from '../../../middlewares/cache' 10import { cacheRoute } from '../../../middlewares/cache'
11import { VideoFileModel } from '../../../models/video/video-file' 11import { VideoFileModel } from '../../../models/video/video-file'
12import { CONFIG } from '../../../initializers/config' 12import { CONFIG } from '../../../initializers/config'
13import { VideoRedundancyStrategyWithManual } from '@shared/models'
13 14
14const statsRouter = express.Router() 15const statsRouter = express.Router()
15 16
@@ -21,12 +22,20 @@ statsRouter.get('/stats',
21async function getStats (req: express.Request, res: express.Response) { 22async 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'
2import * as RateLimit from 'express-rate-limit' 2import * as RateLimit from 'express-rate-limit'
3import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' 3import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared'
4import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
5import { getFormattedObjects } from '../../../helpers/utils' 5import { generateRandomString, getFormattedObjects } from '../../../helpers/utils'
6import { WEBSERVER } from '../../../initializers/constants' 6import { WEBSERVER } from '../../../initializers/constants'
7import { Emailer } from '../../../lib/emailer' 7import { Emailer } from '../../../lib/emailer'
8import { Redis } from '../../../lib/redis' 8import { 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'
29import { 28import {
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'
37import { UserModel } from '../../../models/account/user' 36import { UserModel } from '../../../models/account/user'
38import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 37import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
@@ -50,16 +49,10 @@ import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
50import { UserRegister } from '../../../../shared/models/users/user-register.model' 49import { UserRegister } from '../../../../shared/models/users/user-register.model'
51import { MUser, MUserAccountDefault } from '@server/typings/models' 50import { MUser, MUserAccountDefault } from '@server/typings/models'
52import { Hooks } from '@server/lib/plugins/hooks' 51import { Hooks } from '@server/lib/plugins/hooks'
52import { tokensRouter } from '@server/controllers/api/users/token'
53 53
54const auditLogger = auditLoggerFactory('users') 54const auditLogger = auditLoggerFactory('users')
55 55
56// FIXME: https://github.com/nfriedly/express-rate-limit/issues/138
57// @ts-ignore
58const 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
64const signupRateLimiter = RateLimit({ 57const 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
76const usersRouter = express.Router() 69const usersRouter = express.Router()
70usersRouter.use('/', tokensRouter)
77usersRouter.use('/', myNotificationsRouter) 71usersRouter.use('/', myNotificationsRouter)
78usersRouter.use('/', mySubscriptionsRouter) 72usersRouter.use('/', mySubscriptionsRouter)
79usersRouter.use('/', myBlocklistRouter) 73usersRouter.use('/', myBlocklistRouter)
@@ -170,13 +164,6 @@ usersRouter.post('/:id/verify-email',
170 asyncMiddleware(verifyUserEmail) 164 asyncMiddleware(verifyUserEmail)
171) 165)
172 166
173usersRouter.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
182export { 169export {
@@ -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
372function 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
378async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { 373async 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)
40meRouter.delete('/me', 40meRouter.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
216async function updateMyAvatar (req: express.Request, res: express.Response) { 216async 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 {
74async function listBlockedAccounts (req: express.Request, res: express.Response) { 74async 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) {
99async function listBlockedServers (req: express.Request, res: express.Response) { 105async 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'
10import { getFormattedObjects } from '../../../helpers/utils' 10import { getFormattedObjects } from '../../../helpers/utils'
11import { UserVideoHistoryModel } from '../../../models/account/user-video-history' 11import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
12import { sequelizeTypescript } from '../../../initializers' 12import { sequelizeTypescript } from '../../../initializers/database'
13 13
14const myVideosHistoryRouter = express.Router() 14const 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'
19import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 19import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
20import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 20import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
21import { JobQueue } from '../../../lib/job-queue' 21import { JobQueue } from '../../../lib/job-queue'
22import { logger } from '../../../helpers/logger'
23import { sequelizeTypescript } from '../../../initializers/database' 22import { sequelizeTypescript } from '../../../initializers/database'
24 23
25const mySubscriptionsRouter = express.Router() 24const mySubscriptionsRouter = express.Router()
@@ -52,7 +51,7 @@ mySubscriptionsRouter.get('/me/subscriptions',
52mySubscriptionsRouter.post('/me/subscriptions', 51mySubscriptionsRouter.post('/me/subscriptions',
53 authenticate, 52 authenticate,
54 userSubscriptionAddValidator, 53 userSubscriptionAddValidator,
55 asyncMiddleware(addUserSubscription) 54 addUserSubscription
56) 55)
57 56
58mySubscriptionsRouter.get('/me/subscriptions/:uri', 57mySubscriptionsRouter.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
109async function addUserSubscription (req: express.Request, res: express.Response) { 108function 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 @@
1import { handleLogin, handleTokenRevocation } from '@server/lib/auth'
2import * as RateLimit from 'express-rate-limit'
3import { CONFIG } from '@server/initializers/config'
4import * as express from 'express'
5import { Hooks } from '@server/lib/plugins/hooks'
6import { asyncMiddleware, authenticate } from '@server/middlewares'
7
8const tokensRouter = express.Router()
9
10const loginRateLimiter = RateLimit({
11 windowMs: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS,
12 max: CONFIG.RATES_LIMIT.LOGIN.MAX
13})
14
15tokensRouter.post('/token',
16 loginRateLimiter,
17 handleLogin,
18 tokenSuccess
19)
20
21tokensRouter.post('/revoke-token',
22 authenticate,
23 asyncMiddleware(handleTokenRevocation)
24)
25
26// ---------------------------------------------------------------------------
27
28export {
29 tokensRouter
30}
31// ---------------------------------------------------------------------------
32
33function 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects, getServerActor } from '../../helpers/utils' 2import { getFormattedObjects } from '../../helpers/utils'
3import { 3import {
4 asyncMiddleware, 4 asyncMiddleware,
5 asyncRetryTransactionMiddleware, 5 asyncRetryTransactionMiddleware,
@@ -21,7 +21,7 @@ import { sendUpdateActor } from '../../lib/activitypub/send'
21import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' 21import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared'
22import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' 22import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel'
23import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 23import { buildNSFWFilter, createReqFiles, getCountVideos, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
24import { setAsyncActorKeys } from '../../lib/activitypub' 24import { setAsyncActorKeys } from '../../lib/activitypub/actor'
25import { AccountModel } from '../../models/account/account' 25import { AccountModel } from '../../models/account/account'
26import { MIMETYPES } from '../../initializers/constants' 26import { MIMETYPES } from '../../initializers/constants'
27import { logger } from '../../helpers/logger' 27import { logger } from '../../helpers/logger'
@@ -36,6 +36,7 @@ import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validator
36import { CONFIG } from '../../initializers/config' 36import { CONFIG } from '../../initializers/config'
37import { sequelizeTypescript } from '../../initializers/database' 37import { sequelizeTypescript } from '../../initializers/database'
38import { MChannelAccountDefault } from '@server/typings/models' 38import { MChannelAccountDefault } from '@server/typings/models'
39import { getServerActor } from '@server/models/application/application'
39 40
40const auditLogger = auditLoggerFactory('channels') 41const auditLogger = auditLoggerFactory('channels')
41const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) 42const 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
121async function updateVideoChannelAvatar (req: express.Request, res: express.Response) { 122async 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects, getServerActor } from '../../helpers/utils' 2import { getFormattedObjects } from '../../helpers/utils'
3import { 3import {
4 asyncMiddleware, 4 asyncMiddleware,
5 asyncRetryTransactionMiddleware, 5 asyncRetryTransactionMiddleware,
@@ -41,6 +41,7 @@ import { CONFIG } from '../../initializers/config'
41import { sequelizeTypescript } from '../../initializers/database' 41import { sequelizeTypescript } from '../../initializers/database'
42import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' 42import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
43import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models' 43import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models'
44import { getServerActor } from '@server/models/application/application'
44 45
45const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) 46const 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
464async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) { 464async 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' 2import { UserRight, VideoAbuseCreate, VideoAbuseState, VideoAbuse } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { sequelizeTypescript } from '../../../initializers' 5import { sequelizeTypescript } from '../../../initializers/database'
6import { 6import {
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'
19import { AccountModel } from '../../../models/account/account' 20import { AccountModel } from '../../../models/account/account'
20import { VideoAbuseModel } from '../../../models/video/video-abuse' 21import { VideoAbuseModel } from '../../../models/video/video-abuse'
@@ -22,6 +23,8 @@ import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-
22import { Notifier } from '../../../lib/notifier' 23import { Notifier } from '../../../lib/notifier'
23import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' 24import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
24import { MVideoAbuseAccountVideo } from '../../../typings/models/video' 25import { MVideoAbuseAccountVideo } from '../../../typings/models/video'
26import { getServerActor } from '@server/models/application/application'
27import { MAccountDefault } from '@server/typings/models'
25 28
26const auditLogger = auditLoggerFactory('abuse') 29const auditLogger = auditLoggerFactory('abuse')
27const abuseVideoRouter = express.Router() 30const 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)
38abuseVideoRouter.put('/:videoId/abuse/:id', 42abuseVideoRouter.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) {
106async function reportVideoAbuse (req: express.Request, res: express.Response) { 118async 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' 2import { blacklistVideo, unblacklistVideo } from '@server/lib/video-blacklist'
3import { UserRight, VideoBlacklistCreate } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 4import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 5import { getFormattedObjects } from '../../../helpers/utils'
6import { sequelizeTypescript } from '../../../initializers/database'
5import { 7import {
6 asyncMiddleware, 8 asyncMiddleware,
7 authenticate, 9 authenticate,
@@ -16,11 +18,6 @@ import {
16 videosBlacklistUpdateValidator 18 videosBlacklistUpdateValidator
17} from '../../../middlewares' 19} from '../../../middlewares'
18import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 20import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
19import { sequelizeTypescript } from '../../../initializers'
20import { Notifier } from '../../../lib/notifier'
21import { sendDeleteVideo } from '../../../lib/activitypub/send'
22import { federateVideoIfNeeded } from '../../../lib/activitypub'
23import { MVideoBlacklistVideo } from '@server/typings/models'
24 21
25const blacklistRouter = express.Router() 22const 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
34blacklistRouter.get('/blacklist', 31blacklistRouter.get('/blacklist',
@@ -64,29 +61,15 @@ export {
64 61
65// --------------------------------------------------------------------------- 62// ---------------------------------------------------------------------------
66 63
67async function addVideoToBlacklist (req: express.Request, res: express.Response) { 64async 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
92async function updateVideoBlacklistController (req: express.Request, res: express.Response) { 75async 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
104async function listBlacklist (req: express.Request, res: express.Response) { 87async 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'
6import { getFormattedObjects } from '../../../helpers/utils' 6import { getFormattedObjects } from '../../../helpers/utils'
7import { VideoCaptionModel } from '../../../models/video/video-caption' 7import { VideoCaptionModel } from '../../../models/video/video-caption'
8import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
9import { federateVideoIfNeeded } from '../../../lib/activitypub' 9import { federateVideoIfNeeded } from '../../../lib/activitypub/videos'
10import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' 10import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
11import { CONFIG } from '../../../initializers/config' 11import { CONFIG } from '../../../initializers/config'
12import { sequelizeTypescript } from '../../../initializers/database' 12import { 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'
4import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model' 4import { VideoCommentCreate } from '../../../../shared/models/videos/video-comment.model'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { getFormattedObjects } from '../../../helpers/utils' 6import { getFormattedObjects } from '../../../helpers/utils'
7import { sequelizeTypescript } from '../../../initializers' 7import { sequelizeTypescript } from '../../../initializers/database'
8import { buildFormattedCommentTree, createVideoComment, markCommentAsDeleted } from '../../../lib/video-comment' 8import { buildFormattedCommentTree, createVideoComment, markCommentAsDeleted } from '../../../lib/video-comment'
9import { 9import {
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'
3import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' 3import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
4import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' 4import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
5import { MIMETYPES } from '../../../initializers/constants' 5import { MIMETYPES } from '../../../initializers/constants'
6import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' 6import { getYoutubeDLInfo, YoutubeDLInfo, getYoutubeDLSubs } from '../../../helpers/youtube-dl'
7import { createReqFiles } from '../../../helpers/express-utils' 7import { createReqFiles } from '../../../helpers/express-utils'
8import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
9import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared' 9import { VideoImportCreate, VideoImportState, VideoPrivacy, VideoState } from '../../../../shared'
10import { VideoModel } from '../../../models/video/video' 10import { VideoModel } from '../../../models/video/video'
11import { getVideoActivityPubUrl } from '../../../lib/activitypub' 11import { VideoCaptionModel } from '../../../models/video/video-caption'
12import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
13import { getVideoActivityPubUrl } from '../../../lib/activitypub/url'
12import { TagModel } from '../../../models/video/tag' 14import { TagModel } from '../../../models/video/tag'
13import { VideoImportModel } from '../../../models/video/video-import' 15import { VideoImportModel } from '../../../models/video/video-import'
14import { JobQueue } from '../../../lib/job-queue/job-queue' 16import { JobQueue } from '../../../lib/job-queue/job-queue'
@@ -21,13 +23,14 @@ import { move, readFile } from 'fs-extra'
21import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' 23import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
22import { CONFIG } from '../../../initializers/config' 24import { CONFIG } from '../../../initializers/config'
23import { sequelizeTypescript } from '../../../initializers/database' 25import { sequelizeTypescript } from '../../../initializers/database'
24import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' 26import { createVideoMiniatureFromExisting, createVideoMiniatureFromUrl } from '../../../lib/thumbnail'
25import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 27import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
26import { 28import {
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
211async function processThumbnail (req: express.Request, video: VideoModel) { 258async 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
280async 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
289async 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
233function insertIntoDB (parameters: { 298function 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { extname } from 'path' 2import { extname } from 'path'
3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' 3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared'
4import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 4import { getMetadataFromFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
5import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
6import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 6import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
7import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 7import { getFormattedObjects } from '../../../helpers/utils'
8import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' 8import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
9import { 9import {
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'
17import { 17import { federateVideoIfNeeded, fetchRemoteVideoDescription } from '../../../lib/activitypub/videos'
18 changeVideoChannelShare,
19 federateVideoIfNeeded,
20 fetchRemoteVideoDescription,
21 getVideoActivityPubUrl
22} from '../../../lib/activitypub'
23import { JobQueue } from '../../../lib/job-queue' 18import { JobQueue } from '../../../lib/job-queue'
24import { Redis } from '../../../lib/redis' 19import { Redis } from '../../../lib/redis'
25import { 20import {
@@ -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'
61import { sequelizeTypescript } from '../../../initializers/database' 57import { sequelizeTypescript } from '../../../initializers/database'
62import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail' 58import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
63import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' 59import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
64import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding'
65import { Hooks } from '../../../lib/plugins/hooks' 60import { Hooks } from '../../../lib/plugins/hooks'
66import { MVideoDetails, MVideoFullLight } from '@server/typings/models' 61import { MVideoDetails, MVideoFullLight } from '@server/typings/models'
67import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent' 62import { createTorrentAndSetInfoHash } from '@server/helpers/webtorrent'
68import { getVideoFilePath } from '@server/lib/video-paths' 63import { getVideoFilePath } from '@server/lib/video-paths'
64import toInt from 'validator/lib/toInt'
65import { addOptimizeOrMergeAudioJob } from '@server/helpers/video'
66import { getServerActor } from '@server/models/application/application'
67import { changeVideoChannelShare } from '@server/lib/activitypub/share'
68import { getVideoActivityPubUrl } from '@server/lib/activitypub/url'
69 69
70const auditLogger = auditLoggerFactory('videos') 70const auditLogger = auditLoggerFactory('videos')
71const videosRouter = express.Router() 71const videosRouter = express.Router()
@@ -128,6 +128,10 @@ videosRouter.get('/:id/description',
128 asyncMiddleware(videosGetValidator), 128 asyncMiddleware(videosGetValidator),
129 asyncMiddleware(getVideoDescription) 129 asyncMiddleware(getVideoDescription)
130) 130)
131videosRouter.get('/:id/metadata/:videoFileId',
132 asyncMiddleware(videoFileMetadataGetValidator),
133 asyncMiddleware(getVideoFileMetadata)
134)
131videosRouter.get('/:id', 135videosRouter.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)
137videosRouter.post('/:id/views', 141videosRouter.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
461async function viewVideo (req: express.Request, res: express.Response) { 447async 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
483async 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
497async function listVideos (req: express.Request, res: express.Response) { 488async 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { logger } from '../../../helpers/logger' 2import { logger } from '../../../helpers/logger'
3import { sequelizeTypescript } from '../../../initializers' 3import { sequelizeTypescript } from '../../../initializers/database'
4import { 4import {
5 asyncMiddleware, 5 asyncMiddleware,
6 asyncRetryTransactionMiddleware, 6 asyncRetryTransactionMiddleware,
@@ -15,7 +15,7 @@ import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ow
15import { VideoChangeOwnershipStatus, VideoState } from '../../../../shared/models/videos' 15import { VideoChangeOwnershipStatus, VideoState } from '../../../../shared/models/videos'
16import { VideoChannelModel } from '../../../models/video/video-channel' 16import { VideoChannelModel } from '../../../models/video/video-channel'
17import { getFormattedObjects } from '../../../helpers/utils' 17import { getFormattedObjects } from '../../../helpers/utils'
18import { changeVideoChannelShare } from '../../../lib/activitypub' 18import { changeVideoChannelShare } from '../../../lib/activitypub/share'
19import { sendUpdateVideo } from '../../../lib/activitypub/send' 19import { sendUpdateVideo } from '../../../lib/activitypub/send'
20import { VideoModel } from '../../../models/video/video' 20import { VideoModel } from '../../../models/video/video'
21import { MVideoFullLight } from '@server/typings/models' 21import { 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'
2import { UserVideoRateUpdate } from '../../../../shared' 2import { UserVideoRateUpdate } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { VIDEO_RATE_TYPES } from '../../../initializers/constants' 4import { VIDEO_RATE_TYPES } from '../../../initializers/constants'
5import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub' 5import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub/video-rates'
6import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares' 6import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares'
7import { AccountModel } from '../../../models/account/account' 7import { AccountModel } from '../../../models/account/account'
8import { AccountVideoRateModel } from '../../../models/account/account-video-rate' 8import { 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 @@
1import * as express from 'express' 1import * as express from 'express'
2import { asyncMiddleware } from '../middlewares' 2import { asyncMiddleware } from '../middlewares'
3import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' 3import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
4import * as sitemapModule from 'sitemap' 4import { SitemapStream, streamToPromise } from 'sitemap'
5import { VideoModel } from '../models/video/video' 5import { VideoModel } from '../models/video/video'
6import { VideoChannelModel } from '../models/video/video-channel' 6import { VideoChannelModel } from '../models/video/video-channel'
7import { AccountModel } from '../models/account/account' 7import { 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
75async function serveServerTranslations (req: express.Request, res: express.Response) { 75function 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'
2import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants' 2import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
3import { join } from 'path' 3import { join } from 'path'
4import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager' 4import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager'
5import { servePluginStaticDirectoryValidator } from '../middlewares/validators/plugins' 5import { getPluginValidator, pluginStaticDirectoryValidator, getExternalAuthValidator } from '../middlewares/validators/plugins'
6import { serveThemeCSSValidator } from '../middlewares/validators/themes' 6import { serveThemeCSSValidator } from '../middlewares/validators/themes'
7import { PluginType } from '../../shared/models/plugins/plugin.type' 7import { PluginType } from '../../shared/models/plugins/plugin.type'
8import { isTestInstance } from '../helpers/core-utils' 8import { isTestInstance } from '../helpers/core-utils'
9import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n' 9import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n'
10import { logger } from '@server/helpers/logger'
10 11
11const sendFileOptions = { 12const 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
27pluginsRouter.get('/plugins/:pluginName/:pluginVersion/auth/:authName',
28 getPluginValidator(PluginType.PLUGIN),
29 getExternalAuthValidator,
30 handleAuthInPlugin
31)
32
26pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)', 33pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
27 servePluginStaticDirectoryValidator(PluginType.PLUGIN), 34 getPluginValidator(PluginType.PLUGIN),
35 pluginStaticDirectoryValidator,
28 servePluginStaticDirectory 36 servePluginStaticDirectory
29) 37)
30 38
31pluginsRouter.get('/plugins/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', 39pluginsRouter.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
45pluginsRouter.use('/plugins/:pluginName/router',
46 getPluginValidator(PluginType.PLUGIN, false),
47 servePluginCustomRoutes
48)
49
50pluginsRouter.use('/plugins/:pluginName/:pluginVersion/router',
51 getPluginValidator(PluginType.PLUGIN),
52 servePluginCustomRoutes
53)
54
36pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)', 55pluginsRouter.get('/themes/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
37 servePluginStaticDirectoryValidator(PluginType.THEME), 56 getPluginValidator(PluginType.THEME),
57 pluginStaticDirectoryValidator,
38 servePluginStaticDirectory 58 servePluginStaticDirectory
39) 59)
40 60
41pluginsRouter.get('/themes/:pluginName/:pluginVersion/client-scripts/:staticEndpoint(*)', 61pluginsRouter.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
115function 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
96function servePluginClientScripts (req: express.Request, res: express.Response) { 124function 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
145function 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 @@
1import * as cors from 'cors' 1import * as cors from 'cors'
2import * as express from 'express' 2import * as express from 'express'
3import { 3import {
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'
14import { cacheRoute } from '../middlewares/cache' 14import { cacheRoute } from '../middlewares/cache'
15import { asyncMiddleware, videosDownloadValidator } from '../middlewares' 15import { asyncMiddleware, videosDownloadValidator } from '../middlewares'
@@ -19,8 +19,7 @@ import { VideoCommentModel } from '../models/video/video-comment'
19import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' 19import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo'
20import { join } from 'path' 20import { join } from 'path'
21import { root } from '../helpers/core-utils' 21import { root } from '../helpers/core-utils'
22import { CONFIG } from '../initializers/config' 22import { CONFIG, isEmailEnabled } from '../initializers/config'
23import { Emailer } from '../lib/emailer'
24import { getPreview, getVideoCaption } from './lazy-static' 23import { getPreview, getVideoCaption } from './lazy-static'
25import { VideoStreamingPlaylistType } from '@shared/models/videos/video-streaming-playlist.type' 24import { VideoStreamingPlaylistType } from '@shared/models/videos/video-streaming-playlist.type'
26import { MVideoFile, MVideoFullLight } from '@server/typings/models' 25import { MVideoFile, MVideoFullLight } from '@server/typings/models'
@@ -45,12 +44,12 @@ staticRouter.use(
45staticRouter.use( 44staticRouter.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)
50staticRouter.use( 49staticRouter.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(
68staticRouter.use( 67staticRouter.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
74staticRouter.use( 73staticRouter.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
328async function downloadTorrent (req: express.Request, res: express.Response) { 333function 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
337async function downloadHLSVideoFileTorrent (req: express.Request, res: express.Response) { 342function 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
349async function downloadVideoFile (req: express.Request, res: express.Response) { 354function 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
358async function downloadHLSVideoFile (req: express.Request, res: express.Response) { 363function 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'
6import { Server as WebSocketServer } from 'ws' 6import { Server as WebSocketServer } from 'ws'
7import { TRACKER_RATE_LIMITS } from '../initializers/constants' 7import { TRACKER_RATE_LIMITS } from '../initializers/constants'
8import { VideoFileModel } from '../models/video/video-file' 8import { VideoFileModel } from '../models/video/video-file'
9import { parse } from 'url'
10import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' 9import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
11import { CONFIG } from '../initializers/config' 10import { 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 @@
1import * as cors from 'cors'
1import * as express from 'express' 2import * as express from 'express'
2import { asyncMiddleware } from '../middlewares' 3import { asyncMiddleware } from '../middlewares'
3import { webfingerValidator } from '../middlewares/validators' 4import { webfingerValidator } from '../middlewares/validators'
4 5
5const webfingerRouter = express.Router() 6const webfingerRouter = express.Router()
6 7
8webfingerRouter.use(cors())
9
7webfingerRouter.get('/.well-known/webfinger', 10webfingerRouter.get('/.well-known/webfinger',
8 asyncMiddleware(webfingerValidator), 11 asyncMiddleware(webfingerValidator),
9 webfingerController 12 webfingerController
@@ -18,7 +21,7 @@ export {
18// --------------------------------------------------------------------------- 21// ---------------------------------------------------------------------------
19 22
20function webfingerController (req: express.Request, res: express.Response) { 23function 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}