aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers')
-rw-r--r--server/controllers/activitypub/client.ts223
-rw-r--r--server/controllers/activitypub/inbox.ts9
-rw-r--r--server/controllers/activitypub/outbox.ts4
-rw-r--r--server/controllers/api/accounts.ts97
-rw-r--r--server/controllers/api/config.ts44
-rw-r--r--server/controllers/api/index.ts4
-rw-r--r--server/controllers/api/oauth-clients.ts2
-rw-r--r--server/controllers/api/overviews.ts4
-rw-r--r--server/controllers/api/server/debug.ts25
-rw-r--r--server/controllers/api/server/follows.ts71
-rw-r--r--server/controllers/api/server/index.ts4
-rw-r--r--server/controllers/api/server/logs.ts95
-rw-r--r--server/controllers/api/server/redundancy.ts5
-rw-r--r--server/controllers/api/server/server-blocklist.ts10
-rw-r--r--server/controllers/api/server/stats.ts3
-rw-r--r--server/controllers/api/users/index.ts78
-rw-r--r--server/controllers/api/users/me.ts38
-rw-r--r--server/controllers/api/users/my-blocklist.ts19
-rw-r--r--server/controllers/api/users/my-history.ts6
-rw-r--r--server/controllers/api/users/my-notifications.ts15
-rw-r--r--server/controllers/api/users/my-subscriptions.ts20
-rw-r--r--server/controllers/api/users/my-video-playlists.ts46
-rw-r--r--server/controllers/api/video-channel.ts55
-rw-r--r--server/controllers/api/video-playlist.ts445
-rw-r--r--server/controllers/api/videos/abuse.ts12
-rw-r--r--server/controllers/api/videos/blacklist.ts34
-rw-r--r--server/controllers/api/videos/captions.ts11
-rw-r--r--server/controllers/api/videos/comment.ts23
-rw-r--r--server/controllers/api/videos/import.ts71
-rw-r--r--server/controllers/api/videos/index.ts122
-rw-r--r--server/controllers/api/videos/ownership.ts29
-rw-r--r--server/controllers/api/videos/rate.ts8
-rw-r--r--server/controllers/api/videos/watching.ts3
-rw-r--r--server/controllers/bots.ts16
-rw-r--r--server/controllers/client.ts16
-rw-r--r--server/controllers/feeds.ts24
-rw-r--r--server/controllers/services.ts13
-rw-r--r--server/controllers/static.ts38
-rw-r--r--server/controllers/tracker.ts49
-rw-r--r--server/controllers/webfinger.ts5
40 files changed, 1379 insertions, 417 deletions
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts
index 1a4e28dc8..d36d10de1 100644
--- a/server/controllers/activitypub/client.ts
+++ b/server/controllers/activitypub/client.ts
@@ -2,28 +2,23 @@
2import * as express from 'express' 2import * as express from 'express'
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 { CONFIG, ROUTE_CACHE_LIFETIME } from '../../initializers' 5import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants'
6import { buildAnnounceWithVideoAudience, buildDislikeActivity, buildLikeActivity } from '../../lib/activitypub/send' 6import { buildAnnounceWithVideoAudience, buildLikeActivity } from '../../lib/activitypub/send'
7import { audiencify, getAudience } from '../../lib/activitypub/audience' 7import { audiencify, getAudience } from '../../lib/activitypub/audience'
8import { buildCreateActivity } from '../../lib/activitypub/send/send-create' 8import { buildCreateActivity } from '../../lib/activitypub/send/send-create'
9import { 9import {
10 asyncMiddleware, 10 asyncMiddleware,
11 videosShareValidator,
12 executeIfActivityPub, 11 executeIfActivityPub,
13 localAccountValidator, 12 localAccountValidator,
14 localVideoChannelValidator, 13 localVideoChannelValidator,
15 videosCustomGetValidator 14 videosCustomGetValidator,
15 videosShareValidator
16} from '../../middlewares' 16} from '../../middlewares'
17import { 17import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators'
18 getAccountVideoRateValidator,
19 videoCommentGetValidator,
20 videosGetValidator
21} from '../../middlewares/validators'
22import { AccountModel } from '../../models/account/account' 18import { AccountModel } from '../../models/account/account'
23import { ActorModel } from '../../models/activitypub/actor' 19import { ActorModel } from '../../models/activitypub/actor'
24import { ActorFollowModel } from '../../models/activitypub/actor-follow' 20import { ActorFollowModel } from '../../models/activitypub/actor-follow'
25import { VideoModel } from '../../models/video/video' 21import { VideoModel } from '../../models/video/video'
26import { VideoChannelModel } from '../../models/video/video-channel'
27import { VideoCommentModel } from '../../models/video/video-comment' 22import { VideoCommentModel } from '../../models/video/video-comment'
28import { VideoShareModel } from '../../models/video/video-share' 23import { VideoShareModel } from '../../models/video/video-share'
29import { cacheRoute } from '../../middlewares/cache' 24import { cacheRoute } from '../../middlewares/cache'
@@ -37,87 +32,129 @@ import {
37 getVideoSharesActivityPubUrl 32 getVideoSharesActivityPubUrl
38} from '../../lib/activitypub' 33} from '../../lib/activitypub'
39import { VideoCaptionModel } from '../../models/video/video-caption' 34import { VideoCaptionModel } from '../../models/video/video-caption'
40import { videoRedundancyGetValidator } from '../../middlewares/validators/redundancy' 35import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy'
41import { getServerActor } from '../../helpers/utils' 36import { getServerActor } from '../../helpers/utils'
42import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' 37import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike'
38import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists'
39import { VideoPlaylistModel } from '../../models/video/video-playlist'
40import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
43 41
44const activityPubClientRouter = express.Router() 42const activityPubClientRouter = express.Router()
45 43
46activityPubClientRouter.get('/accounts?/:name', 44activityPubClientRouter.get('/accounts?/:name',
47 executeIfActivityPub(asyncMiddleware(localAccountValidator)), 45 executeIfActivityPub,
48 executeIfActivityPub(accountController) 46 asyncMiddleware(localAccountValidator),
47 accountController
49) 48)
50activityPubClientRouter.get('/accounts?/:name/followers', 49activityPubClientRouter.get('/accounts?/:name/followers',
51 executeIfActivityPub(asyncMiddleware(localAccountValidator)), 50 executeIfActivityPub,
52 executeIfActivityPub(asyncMiddleware(accountFollowersController)) 51 asyncMiddleware(localAccountValidator),
52 asyncMiddleware(accountFollowersController)
53) 53)
54activityPubClientRouter.get('/accounts?/:name/following', 54activityPubClientRouter.get('/accounts?/:name/following',
55 executeIfActivityPub(asyncMiddleware(localAccountValidator)), 55 executeIfActivityPub,
56 executeIfActivityPub(asyncMiddleware(accountFollowingController)) 56 asyncMiddleware(localAccountValidator),
57 asyncMiddleware(accountFollowingController)
58)
59activityPubClientRouter.get('/accounts?/:name/playlists',
60 executeIfActivityPub,
61 asyncMiddleware(localAccountValidator),
62 asyncMiddleware(accountPlaylistsController)
57) 63)
58activityPubClientRouter.get('/accounts?/:name/likes/:videoId', 64activityPubClientRouter.get('/accounts?/:name/likes/:videoId',
59 executeIfActivityPub(asyncMiddleware(getAccountVideoRateValidator('like'))), 65 executeIfActivityPub,
60 executeIfActivityPub(getAccountVideoRate('like')) 66 asyncMiddleware(getAccountVideoRateValidator('like')),
67 getAccountVideoRate('like')
61) 68)
62activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', 69activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId',
63 executeIfActivityPub(asyncMiddleware(getAccountVideoRateValidator('dislike'))), 70 executeIfActivityPub,
64 executeIfActivityPub(getAccountVideoRate('dislike')) 71 asyncMiddleware(getAccountVideoRateValidator('dislike')),
72 getAccountVideoRate('dislike')
65) 73)
66 74
67activityPubClientRouter.get('/videos/watch/:id', 75activityPubClientRouter.get('/videos/watch/:id',
68 executeIfActivityPub(asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS))), 76 executeIfActivityPub,
69 executeIfActivityPub(asyncMiddleware(videosGetValidator)), 77 asyncMiddleware(cacheRoute(ROUTE_CACHE_LIFETIME.ACTIVITY_PUB.VIDEOS)),
70 executeIfActivityPub(asyncMiddleware(videoController)) 78 asyncMiddleware(videosCustomGetValidator('only-video-with-rights')),
79 asyncMiddleware(videoController)
71) 80)
72activityPubClientRouter.get('/videos/watch/:id/activity', 81activityPubClientRouter.get('/videos/watch/:id/activity',
73 executeIfActivityPub(asyncMiddleware(videosGetValidator)), 82 executeIfActivityPub,
74 executeIfActivityPub(asyncMiddleware(videoController)) 83 asyncMiddleware(videosCustomGetValidator('only-video-with-rights')),
84 asyncMiddleware(videoController)
75) 85)
76activityPubClientRouter.get('/videos/watch/:id/announces', 86activityPubClientRouter.get('/videos/watch/:id/announces',
77 executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), 87 executeIfActivityPub,
78 executeIfActivityPub(asyncMiddleware(videoAnnouncesController)) 88 asyncMiddleware(videosCustomGetValidator('only-video')),
89 asyncMiddleware(videoAnnouncesController)
79) 90)
80activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', 91activityPubClientRouter.get('/videos/watch/:id/announces/:actorId',
81 executeIfActivityPub(asyncMiddleware(videosShareValidator)), 92 executeIfActivityPub,
82 executeIfActivityPub(asyncMiddleware(videoAnnounceController)) 93 asyncMiddleware(videosShareValidator),
94 asyncMiddleware(videoAnnounceController)
83) 95)
84activityPubClientRouter.get('/videos/watch/:id/likes', 96activityPubClientRouter.get('/videos/watch/:id/likes',
85 executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), 97 executeIfActivityPub,
86 executeIfActivityPub(asyncMiddleware(videoLikesController)) 98 asyncMiddleware(videosCustomGetValidator('only-video')),
99 asyncMiddleware(videoLikesController)
87) 100)
88activityPubClientRouter.get('/videos/watch/:id/dislikes', 101activityPubClientRouter.get('/videos/watch/:id/dislikes',
89 executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), 102 executeIfActivityPub,
90 executeIfActivityPub(asyncMiddleware(videoDislikesController)) 103 asyncMiddleware(videosCustomGetValidator('only-video')),
104 asyncMiddleware(videoDislikesController)
91) 105)
92activityPubClientRouter.get('/videos/watch/:id/comments', 106activityPubClientRouter.get('/videos/watch/:id/comments',
93 executeIfActivityPub(asyncMiddleware(videosCustomGetValidator('only-video'))), 107 executeIfActivityPub,
94 executeIfActivityPub(asyncMiddleware(videoCommentsController)) 108 asyncMiddleware(videosCustomGetValidator('only-video')),
109 asyncMiddleware(videoCommentsController)
95) 110)
96activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', 111activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId',
97 executeIfActivityPub(asyncMiddleware(videoCommentGetValidator)), 112 executeIfActivityPub,
98 executeIfActivityPub(asyncMiddleware(videoCommentController)) 113 asyncMiddleware(videoCommentGetValidator),
114 asyncMiddleware(videoCommentController)
99) 115)
100activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity', 116activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity',
101 executeIfActivityPub(asyncMiddleware(videoCommentGetValidator)), 117 executeIfActivityPub,
102 executeIfActivityPub(asyncMiddleware(videoCommentController)) 118 asyncMiddleware(videoCommentGetValidator),
119 asyncMiddleware(videoCommentController)
103) 120)
104 121
105activityPubClientRouter.get('/video-channels/:name', 122activityPubClientRouter.get('/video-channels/:name',
106 executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), 123 executeIfActivityPub,
107 executeIfActivityPub(asyncMiddleware(videoChannelController)) 124 asyncMiddleware(localVideoChannelValidator),
125 asyncMiddleware(videoChannelController)
108) 126)
109activityPubClientRouter.get('/video-channels/:name/followers', 127activityPubClientRouter.get('/video-channels/:name/followers',
110 executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), 128 executeIfActivityPub,
111 executeIfActivityPub(asyncMiddleware(videoChannelFollowersController)) 129 asyncMiddleware(localVideoChannelValidator),
130 asyncMiddleware(videoChannelFollowersController)
112) 131)
113activityPubClientRouter.get('/video-channels/:name/following', 132activityPubClientRouter.get('/video-channels/:name/following',
114 executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), 133 executeIfActivityPub,
115 executeIfActivityPub(asyncMiddleware(videoChannelFollowingController)) 134 asyncMiddleware(localVideoChannelValidator),
135 asyncMiddleware(videoChannelFollowingController)
116) 136)
117 137
118activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?', 138activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?',
119 executeIfActivityPub(asyncMiddleware(videoRedundancyGetValidator)), 139 executeIfActivityPub,
120 executeIfActivityPub(asyncMiddleware(videoRedundancyController)) 140 asyncMiddleware(videoFileRedundancyGetValidator),
141 asyncMiddleware(videoRedundancyController)
142)
143activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId',
144 executeIfActivityPub,
145 asyncMiddleware(videoPlaylistRedundancyGetValidator),
146 asyncMiddleware(videoRedundancyController)
147)
148
149activityPubClientRouter.get('/video-playlists/:playlistId',
150 executeIfActivityPub,
151 asyncMiddleware(videoPlaylistsGetValidator),
152 asyncMiddleware(videoPlaylistController)
153)
154activityPubClientRouter.get('/video-playlists/:playlistId/:videoId',
155 executeIfActivityPub,
156 asyncMiddleware(videoPlaylistElementAPGetValidator),
157 asyncMiddleware(videoPlaylistElementController)
121) 158)
122 159
123// --------------------------------------------------------------------------- 160// ---------------------------------------------------------------------------
@@ -128,44 +165,52 @@ export {
128 165
129// --------------------------------------------------------------------------- 166// ---------------------------------------------------------------------------
130 167
131function accountController (req: express.Request, res: express.Response, next: express.NextFunction) { 168function accountController (req: express.Request, res: express.Response) {
132 const account: AccountModel = res.locals.account 169 const account = res.locals.account
133 170
134 return activityPubResponse(activityPubContextify(account.toActivityPubObject()), res) 171 return activityPubResponse(activityPubContextify(account.toActivityPubObject()), res)
135} 172}
136 173
137async function accountFollowersController (req: express.Request, res: express.Response, next: express.NextFunction) { 174async function accountFollowersController (req: express.Request, res: express.Response) {
138 const account: AccountModel = res.locals.account 175 const account = res.locals.account
139 const activityPubResult = await actorFollowers(req, account.Actor) 176 const activityPubResult = await actorFollowers(req, account.Actor)
140 177
141 return activityPubResponse(activityPubContextify(activityPubResult), res) 178 return activityPubResponse(activityPubContextify(activityPubResult), res)
142} 179}
143 180
144async function accountFollowingController (req: express.Request, res: express.Response, next: express.NextFunction) { 181async function accountFollowingController (req: express.Request, res: express.Response) {
145 const account: AccountModel = res.locals.account 182 const account = res.locals.account
146 const activityPubResult = await actorFollowing(req, account.Actor) 183 const activityPubResult = await actorFollowing(req, account.Actor)
147 184
148 return activityPubResponse(activityPubContextify(activityPubResult), res) 185 return activityPubResponse(activityPubContextify(activityPubResult), res)
149} 186}
150 187
188async function accountPlaylistsController (req: express.Request, res: express.Response) {
189 const account = res.locals.account
190 const activityPubResult = await actorPlaylists(req, account)
191
192 return activityPubResponse(activityPubContextify(activityPubResult), res)
193}
194
151function getAccountVideoRate (rateType: VideoRateType) { 195function getAccountVideoRate (rateType: VideoRateType) {
152 return (req: express.Request, res: express.Response) => { 196 return (req: express.Request, res: express.Response) => {
153 const accountVideoRate: AccountVideoRateModel = res.locals.accountVideoRate 197 const accountVideoRate = res.locals.accountVideoRate
154 198
155 const byActor = accountVideoRate.Account.Actor 199 const byActor = accountVideoRate.Account.Actor
156 const url = getRateUrl(rateType, byActor, accountVideoRate.Video) 200 const url = getRateUrl(rateType, byActor, accountVideoRate.Video)
157 const APObject = rateType === 'like' 201 const APObject = rateType === 'like'
158 ? buildLikeActivity(url, byActor, accountVideoRate.Video) 202 ? buildLikeActivity(url, byActor, accountVideoRate.Video)
159 : buildCreateActivity(url, byActor, buildDislikeActivity(url, byActor, accountVideoRate.Video)) 203 : buildDislikeActivity(url, byActor, accountVideoRate.Video)
160 204
161 return activityPubResponse(activityPubContextify(APObject), res) 205 return activityPubResponse(activityPubContextify(APObject), res)
162 } 206 }
163} 207}
164 208
165async function videoController (req: express.Request, res: express.Response) { 209async function videoController (req: express.Request, res: express.Response) {
166 const video: VideoModel = res.locals.video 210 // We need more attributes
211 const video = await VideoModel.loadForGetAPI(res.locals.video.id)
167 212
168 if (video.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(video.url) 213 if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url)
169 214
170 // We need captions to render AP object 215 // We need captions to render AP object
171 video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id) 216 video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id)
@@ -182,9 +227,9 @@ async function videoController (req: express.Request, res: express.Response) {
182} 227}
183 228
184async function videoAnnounceController (req: express.Request, res: express.Response) { 229async function videoAnnounceController (req: express.Request, res: express.Response) {
185 const share = res.locals.videoShare as VideoShareModel 230 const share = res.locals.videoShare
186 231
187 if (share.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(share.url) 232 if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url)
188 233
189 const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined) 234 const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined)
190 235
@@ -192,7 +237,7 @@ async function videoAnnounceController (req: express.Request, res: express.Respo
192} 237}
193 238
194async function videoAnnouncesController (req: express.Request, res: express.Response) { 239async function videoAnnouncesController (req: express.Request, res: express.Response) {
195 const video: VideoModel = res.locals.video 240 const video = res.locals.video
196 241
197 const handler = async (start: number, count: number) => { 242 const handler = async (start: number, count: number) => {
198 const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) 243 const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count)
@@ -207,21 +252,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp
207} 252}
208 253
209async function videoLikesController (req: express.Request, res: express.Response) { 254async function videoLikesController (req: express.Request, res: express.Response) {
210 const video: VideoModel = res.locals.video 255 const video = res.locals.video
211 const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) 256 const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video))
212 257
213 return activityPubResponse(activityPubContextify(json), res) 258 return activityPubResponse(activityPubContextify(json), res)
214} 259}
215 260
216async function videoDislikesController (req: express.Request, res: express.Response) { 261async function videoDislikesController (req: express.Request, res: express.Response) {
217 const video: VideoModel = res.locals.video 262 const video = res.locals.video
218 const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) 263 const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video))
219 264
220 return activityPubResponse(activityPubContextify(json), res) 265 return activityPubResponse(activityPubContextify(json), res)
221} 266}
222 267
223async function videoCommentsController (req: express.Request, res: express.Response) { 268async function videoCommentsController (req: express.Request, res: express.Response) {
224 const video: VideoModel = res.locals.video 269 const video = res.locals.video
225 270
226 const handler = async (start: number, count: number) => { 271 const handler = async (start: number, count: number) => {
227 const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) 272 const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count)
@@ -236,29 +281,29 @@ async function videoCommentsController (req: express.Request, res: express.Respo
236} 281}
237 282
238async function videoChannelController (req: express.Request, res: express.Response) { 283async function videoChannelController (req: express.Request, res: express.Response) {
239 const videoChannel: VideoChannelModel = res.locals.videoChannel 284 const videoChannel = res.locals.videoChannel
240 285
241 return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res) 286 return activityPubResponse(activityPubContextify(videoChannel.toActivityPubObject()), res)
242} 287}
243 288
244async function videoChannelFollowersController (req: express.Request, res: express.Response) { 289async function videoChannelFollowersController (req: express.Request, res: express.Response) {
245 const videoChannel: VideoChannelModel = res.locals.videoChannel 290 const videoChannel = res.locals.videoChannel
246 const activityPubResult = await actorFollowers(req, videoChannel.Actor) 291 const activityPubResult = await actorFollowers(req, videoChannel.Actor)
247 292
248 return activityPubResponse(activityPubContextify(activityPubResult), res) 293 return activityPubResponse(activityPubContextify(activityPubResult), res)
249} 294}
250 295
251async function videoChannelFollowingController (req: express.Request, res: express.Response) { 296async function videoChannelFollowingController (req: express.Request, res: express.Response) {
252 const videoChannel: VideoChannelModel = res.locals.videoChannel 297 const videoChannel = res.locals.videoChannel
253 const activityPubResult = await actorFollowing(req, videoChannel.Actor) 298 const activityPubResult = await actorFollowing(req, videoChannel.Actor)
254 299
255 return activityPubResponse(activityPubContextify(activityPubResult), res) 300 return activityPubResponse(activityPubContextify(activityPubResult), res)
256} 301}
257 302
258async function videoCommentController (req: express.Request, res: express.Response) { 303async function videoCommentController (req: express.Request, res: express.Response) {
259 const videoComment: VideoCommentModel = res.locals.videoComment 304 const videoComment = res.locals.videoComment
260 305
261 if (videoComment.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(videoComment.url) 306 if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url)
262 307
263 const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, undefined) 308 const threadParentComments = await VideoCommentModel.listThreadParentComments(videoComment, undefined)
264 const isPublic = true // Comments are always public 309 const isPublic = true // Comments are always public
@@ -275,8 +320,8 @@ async function videoCommentController (req: express.Request, res: express.Respon
275} 320}
276 321
277async function videoRedundancyController (req: express.Request, res: express.Response) { 322async function videoRedundancyController (req: express.Request, res: express.Response) {
278 const videoRedundancy: VideoRedundancyModel = res.locals.videoRedundancy 323 const videoRedundancy = res.locals.videoRedundancy
279 if (videoRedundancy.url.startsWith(CONFIG.WEBSERVER.URL) === false) return res.redirect(videoRedundancy.url) 324 if (videoRedundancy.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoRedundancy.url)
280 325
281 const serverActor = await getServerActor() 326 const serverActor = await getServerActor()
282 327
@@ -291,6 +336,26 @@ async function videoRedundancyController (req: express.Request, res: express.Res
291 return activityPubResponse(activityPubContextify(object), res) 336 return activityPubResponse(activityPubContextify(object), res)
292} 337}
293 338
339async function videoPlaylistController (req: express.Request, res: express.Response) {
340 const playlist = res.locals.videoPlaylist
341
342 // We need more attributes
343 playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId)
344
345 const json = await playlist.toActivityPubObject(req.query.page, null)
346 const audience = getAudience(playlist.OwnerAccount.Actor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC)
347 const object = audiencify(json, audience)
348
349 return activityPubResponse(activityPubContextify(object), res)
350}
351
352async function videoPlaylistElementController (req: express.Request, res: express.Response) {
353 const videoPlaylistElement = res.locals.videoPlaylistElement
354
355 const json = videoPlaylistElement.toActivityPubObject()
356 return activityPubResponse(activityPubContextify(json), res)
357}
358
294// --------------------------------------------------------------------------- 359// ---------------------------------------------------------------------------
295 360
296async function actorFollowing (req: express.Request, actor: ActorModel) { 361async function actorFollowing (req: express.Request, actor: ActorModel) {
@@ -298,15 +363,23 @@ async function actorFollowing (req: express.Request, actor: ActorModel) {
298 return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) 363 return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count)
299 } 364 }
300 365
301 return activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.path, handler, req.query.page) 366 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
302} 367}
303 368
304async function actorFollowers (req: express.Request, actor: ActorModel) { 369async function actorFollowers (req: express.Request, actor: ActorModel) {
305 const handler = (start: number, count: number) => { 370 const handler = (start: number, count: number) => {
306 return ActorFollowModel.listAcceptedFollowerUrlsForApi([ actor.id ], undefined, start, count) 371 return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count)
372 }
373
374 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
375}
376
377async function actorPlaylists (req: express.Request, account: AccountModel) {
378 const handler = (start: number, count: number) => {
379 return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count)
307 } 380 }
308 381
309 return activityPubCollectionPagination(CONFIG.WEBSERVER.URL + req.path, handler, req.query.page) 382 return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page)
310} 383}
311 384
312function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) { 385function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) {
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts
index f0e65015b..38d5c51df 100644
--- a/server/controllers/activitypub/inbox.ts
+++ b/server/controllers/activitypub/inbox.ts
@@ -5,8 +5,6 @@ import { logger } from '../../helpers/logger'
5import { processActivities } from '../../lib/activitypub/process/process' 5import { processActivities } from '../../lib/activitypub/process/process'
6import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChannelValidator, signatureValidator } from '../../middlewares' 6import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChannelValidator, signatureValidator } from '../../middlewares'
7import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' 7import { activityPubValidator } from '../../middlewares/validators/activitypub/activity'
8import { VideoChannelModel } from '../../models/video/video-channel'
9import { AccountModel } from '../../models/account/account'
10import { queue } from 'async' 8import { queue } from 'async'
11import { ActorModel } from '../../models/activitypub/actor' 9import { ActorModel } from '../../models/activitypub/actor'
12 10
@@ -66,12 +64,7 @@ function inboxController (req: express.Request, res: express.Response) {
66 activities = activities.filter(a => isActivityValid(a)) 64 activities = activities.filter(a => isActivityValid(a))
67 logger.debug('We keep %d activities.', activities.length, { activities }) 65 logger.debug('We keep %d activities.', activities.length, { activities })
68 66
69 let accountOrChannel: VideoChannelModel | AccountModel 67 const accountOrChannel = res.locals.account || res.locals.videoChannel
70 if (res.locals.account) {
71 accountOrChannel = res.locals.account
72 } else if (res.locals.videoChannel) {
73 accountOrChannel = res.locals.videoChannel
74 }
75 68
76 logger.info('Receiving inbox requests for %d activities by %s.', activities.length, res.locals.signature.actor.url) 69 logger.info('Receiving inbox requests for %d activities by %s.', activities.length, res.locals.signature.actor.url)
77 70
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts
index bd0e4fe9d..38b6ec976 100644
--- a/server/controllers/activitypub/outbox.ts
+++ b/server/controllers/activitypub/outbox.ts
@@ -32,8 +32,8 @@ export {
32 32
33// --------------------------------------------------------------------------- 33// ---------------------------------------------------------------------------
34 34
35async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) { 35async function outboxController (req: express.Request, res: express.Response) {
36 const accountOrVideoChannel: AccountModel | VideoChannelModel = res.locals.account || res.locals.videoChannel 36 const accountOrVideoChannel = res.locals.account || res.locals.videoChannel
37 const actor = accountOrVideoChannel.Actor 37 const actor = accountOrVideoChannel.Actor
38 const actorOutboxUrl = actor.url + '/outbox' 38 const actorOutboxUrl = actor.url + '/outbox'
39 39
diff --git a/server/controllers/api/accounts.ts b/server/controllers/api/accounts.ts
index 8c0237203..8d4db1e75 100644
--- a/server/controllers/api/accounts.ts
+++ b/server/controllers/api/accounts.ts
@@ -1,21 +1,32 @@
1import * as express from 'express' 1import * as express from 'express'
2import { getFormattedObjects } from '../../helpers/utils' 2import { getFormattedObjects, getServerActor } from '../../helpers/utils'
3import { 3import {
4 asyncMiddleware, 4 asyncMiddleware,
5 authenticate,
5 commonVideosFiltersValidator, 6 commonVideosFiltersValidator,
6 listVideoAccountChannelsValidator,
7 optionalAuthenticate, 7 optionalAuthenticate,
8 paginationValidator, 8 paginationValidator,
9 setDefaultPagination, 9 setDefaultPagination,
10 setDefaultSort 10 setDefaultSort,
11 videoPlaylistsSortValidator,
12 videoRatesSortValidator,
13 videoRatingValidator
11} from '../../middlewares' 14} from '../../middlewares'
12import { accountsNameWithHostGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' 15import {
16 accountNameWithHostGetValidator,
17 accountsSortValidator,
18 ensureAuthUserOwnsAccountValidator,
19 videosSortValidator
20} from '../../middlewares/validators'
13import { AccountModel } from '../../models/account/account' 21import { AccountModel } from '../../models/account/account'
22import { AccountVideoRateModel } from '../../models/account/account-video-rate'
14import { VideoModel } from '../../models/video/video' 23import { VideoModel } from '../../models/video/video'
15import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 24import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
16import { VideoChannelModel } from '../../models/video/video-channel' 25import { VideoChannelModel } from '../../models/video/video-channel'
17import { JobQueue } from '../../lib/job-queue' 26import { JobQueue } from '../../lib/job-queue'
18import { logger } from '../../helpers/logger' 27import { logger } from '../../helpers/logger'
28import { VideoPlaylistModel } from '../../models/video/video-playlist'
29import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
19 30
20const accountsRouter = express.Router() 31const accountsRouter = express.Router()
21 32
@@ -28,12 +39,12 @@ accountsRouter.get('/',
28) 39)
29 40
30accountsRouter.get('/:accountName', 41accountsRouter.get('/:accountName',
31 asyncMiddleware(accountsNameWithHostGetValidator), 42 asyncMiddleware(accountNameWithHostGetValidator),
32 getAccount 43 getAccount
33) 44)
34 45
35accountsRouter.get('/:accountName/videos', 46accountsRouter.get('/:accountName/videos',
36 asyncMiddleware(accountsNameWithHostGetValidator), 47 asyncMiddleware(accountNameWithHostGetValidator),
37 paginationValidator, 48 paginationValidator,
38 videosSortValidator, 49 videosSortValidator,
39 setDefaultSort, 50 setDefaultSort,
@@ -44,8 +55,31 @@ accountsRouter.get('/:accountName/videos',
44) 55)
45 56
46accountsRouter.get('/:accountName/video-channels', 57accountsRouter.get('/:accountName/video-channels',
47 asyncMiddleware(listVideoAccountChannelsValidator), 58 asyncMiddleware(accountNameWithHostGetValidator),
48 asyncMiddleware(listVideoAccountChannels) 59 asyncMiddleware(listAccountChannels)
60)
61
62accountsRouter.get('/:accountName/video-playlists',
63 optionalAuthenticate,
64 asyncMiddleware(accountNameWithHostGetValidator),
65 paginationValidator,
66 videoPlaylistsSortValidator,
67 setDefaultSort,
68 setDefaultPagination,
69 commonVideoPlaylistFiltersValidator,
70 asyncMiddleware(listAccountPlaylists)
71)
72
73accountsRouter.get('/:accountName/ratings',
74 authenticate,
75 asyncMiddleware(accountNameWithHostGetValidator),
76 ensureAuthUserOwnsAccountValidator,
77 paginationValidator,
78 videoRatesSortValidator,
79 setDefaultSort,
80 setDefaultPagination,
81 videoRatingValidator,
82 asyncMiddleware(listAccountRatings)
49) 83)
50 84
51// --------------------------------------------------------------------------- 85// ---------------------------------------------------------------------------
@@ -56,8 +90,8 @@ export {
56 90
57// --------------------------------------------------------------------------- 91// ---------------------------------------------------------------------------
58 92
59function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { 93function getAccount (req: express.Request, res: express.Response) {
60 const account: AccountModel = res.locals.account 94 const account = res.locals.account
61 95
62 if (account.isOutdated()) { 96 if (account.isOutdated()) {
63 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } }) 97 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'actor', url: account.Actor.url } })
@@ -67,20 +101,42 @@ function getAccount (req: express.Request, res: express.Response, next: express.
67 return res.json(account.toFormattedJSON()) 101 return res.json(account.toFormattedJSON())
68} 102}
69 103
70async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) { 104async function listAccounts (req: express.Request, res: express.Response) {
71 const resultList = await AccountModel.listForApi(req.query.start, req.query.count, req.query.sort) 105 const resultList = await AccountModel.listForApi(req.query.start, req.query.count, req.query.sort)
72 106
73 return res.json(getFormattedObjects(resultList.data, resultList.total)) 107 return res.json(getFormattedObjects(resultList.data, resultList.total))
74} 108}
75 109
76async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) { 110async function listAccountChannels (req: express.Request, res: express.Response) {
77 const resultList = await VideoChannelModel.listByAccount(res.locals.account.id) 111 const resultList = await VideoChannelModel.listByAccount(res.locals.account.id)
78 112
79 return res.json(getFormattedObjects(resultList.data, resultList.total)) 113 return res.json(getFormattedObjects(resultList.data, resultList.total))
80} 114}
81 115
82async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 116async function listAccountPlaylists (req: express.Request, res: express.Response) {
83 const account: AccountModel = res.locals.account 117 const serverActor = await getServerActor()
118
119 // Allow users to see their private/unlisted video playlists
120 let privateAndUnlisted = false
121 if (res.locals.oauth && res.locals.oauth.token.User.Account.id === res.locals.account.id) {
122 privateAndUnlisted = true
123 }
124
125 const resultList = await VideoPlaylistModel.listForApi({
126 followerActorId: serverActor.id,
127 start: req.query.start,
128 count: req.query.count,
129 sort: req.query.sort,
130 accountId: res.locals.account.id,
131 privateAndUnlisted,
132 type: req.query.playlistType
133 })
134
135 return res.json(getFormattedObjects(resultList.data, resultList.total))
136}
137
138async function listAccountVideos (req: express.Request, res: express.Response) {
139 const account = res.locals.account
84 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 140 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
85 141
86 const resultList = await VideoModel.listForApi({ 142 const resultList = await VideoModel.listForApi({
@@ -103,3 +159,16 @@ async function listAccountVideos (req: express.Request, res: express.Response, n
103 159
104 return res.json(getFormattedObjects(resultList.data, resultList.total)) 160 return res.json(getFormattedObjects(resultList.data, resultList.total))
105} 161}
162
163async function listAccountRatings (req: express.Request, res: express.Response) {
164 const account = res.locals.account
165
166 const resultList = await AccountVideoRateModel.listByAccountForApi({
167 accountId: account.id,
168 start: req.query.start,
169 count: req.query.count,
170 sort: req.query.sort,
171 type: req.query.rating
172 })
173 return res.json(getFormattedObjects(resultList.rows, resultList.count))
174}
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 255026f46..40012c03b 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -1,10 +1,10 @@
1import * as express from 'express' 1import * as express from 'express'
2import { omit, snakeCase } from 'lodash' 2import { snakeCase } from 'lodash'
3import { ServerConfig, UserRight } from '../../../shared' 3import { ServerConfig, UserRight } from '../../../shared'
4import { About } from '../../../shared/models/server/about.model' 4import { About } from '../../../shared/models/server/about.model'
5import { CustomConfig } from '../../../shared/models/server/custom-config.model' 5import { CustomConfig } from '../../../shared/models/server/custom-config.model'
6import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' 6import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup'
7import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers' 7import { CONSTRAINTS_FIELDS } from '../../initializers/constants'
8import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' 8import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares'
9import { customConfigUpdateValidator } from '../../middlewares/validators/config' 9import { customConfigUpdateValidator } from '../../middlewares/validators/config'
10import { ClientHtml } from '../../lib/client-html' 10import { ClientHtml } from '../../lib/client-html'
@@ -14,6 +14,7 @@ import { getServerCommit } from '../../helpers/utils'
14import { Emailer } from '../../lib/emailer' 14import { Emailer } from '../../lib/emailer'
15import { isNumeric } from 'validator' 15import { isNumeric } from 'validator'
16import { objectConverter } from '../../helpers/core-utils' 16import { objectConverter } from '../../helpers/core-utils'
17import { CONFIG, reloadConfig } from '../../initializers/config'
17 18
18const packageJSON = require('../../../../package.json') 19const packageJSON = require('../../../../package.json')
19const configRouter = express.Router() 20const configRouter = express.Router()
@@ -58,6 +59,7 @@ async function getConfig (req: express.Request, res: express.Response) {
58 name: CONFIG.INSTANCE.NAME, 59 name: CONFIG.INSTANCE.NAME,
59 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, 60 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
60 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, 61 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
62 isNSFW: CONFIG.INSTANCE.IS_NSFW,
61 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 63 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
62 customizations: { 64 customizations: {
63 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT, 65 javascript: CONFIG.INSTANCE.CUSTOMIZATIONS.JAVASCRIPT,
@@ -78,6 +80,9 @@ async function getConfig (req: express.Request, res: express.Response) {
78 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION 80 requiresEmailVerification: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION
79 }, 81 },
80 transcoding: { 82 transcoding: {
83 hls: {
84 enabled: CONFIG.TRANSCODING.HLS.ENABLED
85 },
81 enabledResolutions 86 enabledResolutions
82 }, 87 },
83 import: { 88 import: {
@@ -90,6 +95,13 @@ async function getConfig (req: express.Request, res: express.Response) {
90 } 95 }
91 } 96 }
92 }, 97 },
98 autoBlacklist: {
99 videos: {
100 ofUsers: {
101 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
102 }
103 }
104 },
93 avatar: { 105 avatar: {
94 file: { 106 file: {
95 size: { 107 size: {
@@ -125,13 +137,16 @@ async function getConfig (req: express.Request, res: express.Response) {
125 videos: { 137 videos: {
126 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS 138 intervalDays: CONFIG.TRENDING.VIDEOS.INTERVAL_DAYS
127 } 139 }
140 },
141 tracker: {
142 enabled: CONFIG.TRACKER.ENABLED
128 } 143 }
129 } 144 }
130 145
131 return res.json(json) 146 return res.json(json)
132} 147}
133 148
134function getAbout (req: express.Request, res: express.Response, next: express.NextFunction) { 149function getAbout (req: express.Request, res: express.Response) {
135 const about: About = { 150 const about: About = {
136 instance: { 151 instance: {
137 name: CONFIG.INSTANCE.NAME, 152 name: CONFIG.INSTANCE.NAME,
@@ -144,13 +159,13 @@ function getAbout (req: express.Request, res: express.Response, next: express.Ne
144 return res.json(about).end() 159 return res.json(about).end()
145} 160}
146 161
147async function getCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 162async function getCustomConfig (req: express.Request, res: express.Response) {
148 const data = customConfig() 163 const data = customConfig()
149 164
150 return res.json(data).end() 165 return res.json(data).end()
151} 166}
152 167
153async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 168async function deleteCustomConfig (req: express.Request, res: express.Response) {
154 await remove(CONFIG.CUSTOM_FILE) 169 await remove(CONFIG.CUSTOM_FILE)
155 170
156 auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig())) 171 auditLogger.delete(getAuditIdFromRes(res), new CustomConfigAuditView(customConfig()))
@@ -163,7 +178,7 @@ async function deleteCustomConfig (req: express.Request, res: express.Response,
163 return res.json(data).end() 178 return res.json(data).end()
164} 179}
165 180
166async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { 181async function updateCustomConfig (req: express.Request, res: express.Response) {
167 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig()) 182 const oldCustomConfigAuditKeys = new CustomConfigAuditView(customConfig())
168 183
169 // camelCase to snake_case key + Force number conversion 184 // camelCase to snake_case key + Force number conversion
@@ -200,6 +215,7 @@ function customConfig (): CustomConfig {
200 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, 215 shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION,
201 description: CONFIG.INSTANCE.DESCRIPTION, 216 description: CONFIG.INSTANCE.DESCRIPTION,
202 terms: CONFIG.INSTANCE.TERMS, 217 terms: CONFIG.INSTANCE.TERMS,
218 isNSFW: CONFIG.INSTANCE.IS_NSFW,
203 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, 219 defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE,
204 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, 220 defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY,
205 customizations: { 221 customizations: {
@@ -246,6 +262,9 @@ function customConfig (): CustomConfig {
246 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ], 262 '480p': CONFIG.TRANSCODING.RESOLUTIONS[ '480p' ],
247 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ], 263 '720p': CONFIG.TRANSCODING.RESOLUTIONS[ '720p' ],
248 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ] 264 '1080p': CONFIG.TRANSCODING.RESOLUTIONS[ '1080p' ]
265 },
266 hls: {
267 enabled: CONFIG.TRANSCODING.HLS.ENABLED
249 } 268 }
250 }, 269 },
251 import: { 270 import: {
@@ -257,6 +276,19 @@ function customConfig (): CustomConfig {
257 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED 276 enabled: CONFIG.IMPORT.VIDEOS.TORRENT.ENABLED
258 } 277 }
259 } 278 }
279 },
280 autoBlacklist: {
281 videos: {
282 ofUsers: {
283 enabled: CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED
284 }
285 }
286 },
287 followers: {
288 instance: {
289 enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED,
290 manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL
291 }
260 } 292 }
261 } 293 }
262} 294}
diff --git a/server/controllers/api/index.ts b/server/controllers/api/index.ts
index 8a58b5466..60a84036e 100644
--- a/server/controllers/api/index.ts
+++ b/server/controllers/api/index.ts
@@ -11,6 +11,7 @@ import { videoChannelRouter } from './video-channel'
11import * as cors from 'cors' 11import * as cors from 'cors'
12import { searchRouter } from './search' 12import { searchRouter } from './search'
13import { overviewsRouter } from './overviews' 13import { overviewsRouter } from './overviews'
14import { videoPlaylistRouter } from './video-playlist'
14 15
15const apiRouter = express.Router() 16const apiRouter = express.Router()
16 17
@@ -26,6 +27,7 @@ apiRouter.use('/config', configRouter)
26apiRouter.use('/users', usersRouter) 27apiRouter.use('/users', usersRouter)
27apiRouter.use('/accounts', accountsRouter) 28apiRouter.use('/accounts', accountsRouter)
28apiRouter.use('/video-channels', videoChannelRouter) 29apiRouter.use('/video-channels', videoChannelRouter)
30apiRouter.use('/video-playlists', videoPlaylistRouter)
29apiRouter.use('/videos', videosRouter) 31apiRouter.use('/videos', videosRouter)
30apiRouter.use('/jobs', jobsRouter) 32apiRouter.use('/jobs', jobsRouter)
31apiRouter.use('/search', searchRouter) 33apiRouter.use('/search', searchRouter)
@@ -39,6 +41,6 @@ export { apiRouter }
39 41
40// --------------------------------------------------------------------------- 42// ---------------------------------------------------------------------------
41 43
42function pong (req: express.Request, res: express.Response, next: express.NextFunction) { 44function pong (req: express.Request, res: express.Response) {
43 return res.send('pong').status(200).end() 45 return res.send('pong').status(200).end()
44} 46}
diff --git a/server/controllers/api/oauth-clients.ts b/server/controllers/api/oauth-clients.ts
index 3dcc023e6..b2de8bcf5 100644
--- a/server/controllers/api/oauth-clients.ts
+++ b/server/controllers/api/oauth-clients.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { OAuthClientLocal } from '../../../shared' 2import { OAuthClientLocal } from '../../../shared'
3import { logger } from '../../helpers/logger' 3import { logger } from '../../helpers/logger'
4import { CONFIG } from '../../initializers' 4import { CONFIG } from '../../initializers/config'
5import { asyncMiddleware } from '../../middlewares' 5import { asyncMiddleware } from '../../middlewares'
6import { OAuthClientModel } from '../../models/oauth/oauth-client' 6import { OAuthClientModel } from '../../models/oauth/oauth-client'
7 7
diff --git a/server/controllers/api/overviews.ts b/server/controllers/api/overviews.ts
index 8b6773056..37ac152db 100644
--- a/server/controllers/api/overviews.ts
+++ b/server/controllers/api/overviews.ts
@@ -4,7 +4,7 @@ import { VideoModel } from '../../models/video/video'
4import { asyncMiddleware } from '../../middlewares' 4import { asyncMiddleware } from '../../middlewares'
5import { TagModel } from '../../models/video/tag' 5import { TagModel } from '../../models/video/tag'
6import { VideosOverview } from '../../../shared/models/overviews' 6import { VideosOverview } from '../../../shared/models/overviews'
7import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers' 7import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers/constants'
8import { cacheRoute } from '../../middlewares/cache' 8import { cacheRoute } from '../../middlewares/cache'
9import * as memoizee from 'memoizee' 9import * as memoizee from 'memoizee'
10 10
@@ -94,7 +94,7 @@ async function getVideos (
94) { 94) {
95 const query = Object.assign({ 95 const query = Object.assign({
96 start: 0, 96 start: 0,
97 count: 10, 97 count: 12,
98 sort: '-createdAt', 98 sort: '-createdAt',
99 includeLocalVideos: true, 99 includeLocalVideos: true,
100 nsfw: buildNSFWFilter(res), 100 nsfw: buildNSFWFilter(res),
diff --git a/server/controllers/api/server/debug.ts b/server/controllers/api/server/debug.ts
new file mode 100644
index 000000000..4450038f6
--- /dev/null
+++ b/server/controllers/api/server/debug.ts
@@ -0,0 +1,25 @@
1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
4
5const debugRouter = express.Router()
6
7debugRouter.get('/debug',
8 authenticate,
9 ensureUserHasRight(UserRight.MANAGE_DEBUG),
10 asyncMiddleware(getDebug)
11)
12
13// ---------------------------------------------------------------------------
14
15export {
16 debugRouter
17}
18
19// ---------------------------------------------------------------------------
20
21async function getDebug (req: express.Request, res: express.Response) {
22 return res.json({
23 ip: req.ip
24 }).end()
25}
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts
index 9fa6c34ba..d38ce91de 100644
--- a/server/controllers/api/server/follows.ts
+++ b/server/controllers/api/server/follows.ts
@@ -2,22 +2,29 @@ import * 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, getServerActor } from '../../../helpers/utils'
5import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers' 5import { SERVER_ACTOR_NAME } from '../../../initializers/constants'
6import { sendUndoFollow } from '../../../lib/activitypub/send' 6import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send'
7import { 7import {
8 asyncMiddleware, 8 asyncMiddleware,
9 authenticate, 9 authenticate,
10 ensureUserHasRight, 10 ensureUserHasRight,
11 paginationValidator, 11 paginationValidator,
12 removeFollowingValidator,
13 setBodyHostsPort, 12 setBodyHostsPort,
14 setDefaultPagination, 13 setDefaultPagination,
15 setDefaultSort 14 setDefaultSort
16} from '../../../middlewares' 15} from '../../../middlewares'
17import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators' 16import {
17 acceptOrRejectFollowerValidator,
18 followersSortValidator,
19 followingSortValidator,
20 followValidator,
21 getFollowerValidator,
22 removeFollowingValidator
23} from '../../../middlewares/validators'
18import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 24import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
19import { JobQueue } from '../../../lib/job-queue' 25import { JobQueue } from '../../../lib/job-queue'
20import { removeRedundancyOf } from '../../../lib/redundancy' 26import { removeRedundancyOf } from '../../../lib/redundancy'
27import { sequelizeTypescript } from '../../../initializers/database'
21 28
22const serverFollowsRouter = express.Router() 29const serverFollowsRouter = express.Router()
23serverFollowsRouter.get('/following', 30serverFollowsRouter.get('/following',
@@ -40,7 +47,7 @@ serverFollowsRouter.delete('/following/:host',
40 authenticate, 47 authenticate,
41 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), 48 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
42 asyncMiddleware(removeFollowingValidator), 49 asyncMiddleware(removeFollowingValidator),
43 asyncMiddleware(removeFollow) 50 asyncMiddleware(removeFollowing)
44) 51)
45 52
46serverFollowsRouter.get('/followers', 53serverFollowsRouter.get('/followers',
@@ -51,6 +58,29 @@ serverFollowsRouter.get('/followers',
51 asyncMiddleware(listFollowers) 58 asyncMiddleware(listFollowers)
52) 59)
53 60
61serverFollowsRouter.delete('/followers/:nameWithHost',
62 authenticate,
63 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
64 asyncMiddleware(getFollowerValidator),
65 asyncMiddleware(removeOrRejectFollower)
66)
67
68serverFollowsRouter.post('/followers/:nameWithHost/reject',
69 authenticate,
70 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
71 asyncMiddleware(getFollowerValidator),
72 acceptOrRejectFollowerValidator,
73 asyncMiddleware(removeOrRejectFollower)
74)
75
76serverFollowsRouter.post('/followers/:nameWithHost/accept',
77 authenticate,
78 ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW),
79 asyncMiddleware(getFollowerValidator),
80 acceptOrRejectFollowerValidator,
81 asyncMiddleware(acceptFollower)
82)
83
54// --------------------------------------------------------------------------- 84// ---------------------------------------------------------------------------
55 85
56export { 86export {
@@ -59,7 +89,7 @@ export {
59 89
60// --------------------------------------------------------------------------- 90// ---------------------------------------------------------------------------
61 91
62async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { 92async function listFollowing (req: express.Request, res: express.Response) {
63 const serverActor = await getServerActor() 93 const serverActor = await getServerActor()
64 const resultList = await ActorFollowModel.listFollowingForApi( 94 const resultList = await ActorFollowModel.listFollowingForApi(
65 serverActor.id, 95 serverActor.id,
@@ -72,7 +102,7 @@ async function listFollowing (req: express.Request, res: express.Response, next:
72 return res.json(getFormattedObjects(resultList.data, resultList.total)) 102 return res.json(getFormattedObjects(resultList.data, resultList.total))
73} 103}
74 104
75async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { 105async function listFollowers (req: express.Request, res: express.Response) {
76 const serverActor = await getServerActor() 106 const serverActor = await getServerActor()
77 const resultList = await ActorFollowModel.listFollowersForApi( 107 const resultList = await ActorFollowModel.listFollowersForApi(
78 serverActor.id, 108 serverActor.id,
@@ -85,7 +115,7 @@ async function listFollowers (req: express.Request, res: express.Response, next:
85 return res.json(getFormattedObjects(resultList.data, resultList.total)) 115 return res.json(getFormattedObjects(resultList.data, resultList.total))
86} 116}
87 117
88async function followInstance (req: express.Request, res: express.Response, next: express.NextFunction) { 118async function followInstance (req: express.Request, res: express.Response) {
89 const hosts = req.body.hosts as string[] 119 const hosts = req.body.hosts as string[]
90 const follower = await getServerActor() 120 const follower = await getServerActor()
91 121
@@ -103,8 +133,8 @@ async function followInstance (req: express.Request, res: express.Response, next
103 return res.status(204).end() 133 return res.status(204).end()
104} 134}
105 135
106async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) { 136async function removeFollowing (req: express.Request, res: express.Response) {
107 const follow: ActorFollowModel = res.locals.follow 137 const follow = res.locals.follow
108 138
109 await sequelizeTypescript.transaction(async t => { 139 await sequelizeTypescript.transaction(async t => {
110 if (follow.state === 'accepted') await sendUndoFollow(follow, t) 140 if (follow.state === 'accepted') await sendUndoFollow(follow, t)
@@ -123,3 +153,24 @@ async function removeFollow (req: express.Request, res: express.Response, next:
123 153
124 return res.status(204).end() 154 return res.status(204).end()
125} 155}
156
157async function removeOrRejectFollower (req: express.Request, res: express.Response) {
158 const follow = res.locals.follow
159
160 await sendReject(follow.ActorFollower, follow.ActorFollowing)
161
162 await follow.destroy()
163
164 return res.status(204).end()
165}
166
167async function acceptFollower (req: express.Request, res: express.Response) {
168 const follow = res.locals.follow
169
170 await sendAccept(follow)
171
172 follow.state = 'accepted'
173 await follow.save()
174
175 return res.status(204).end()
176}
diff --git a/server/controllers/api/server/index.ts b/server/controllers/api/server/index.ts
index 814248e5f..6b8793a19 100644
--- a/server/controllers/api/server/index.ts
+++ b/server/controllers/api/server/index.ts
@@ -4,6 +4,8 @@ import { statsRouter } from './stats'
4import { serverRedundancyRouter } from './redundancy' 4import { serverRedundancyRouter } from './redundancy'
5import { serverBlocklistRouter } from './server-blocklist' 5import { serverBlocklistRouter } from './server-blocklist'
6import { contactRouter } from './contact' 6import { contactRouter } from './contact'
7import { logsRouter } from './logs'
8import { debugRouter } from './debug'
7 9
8const serverRouter = express.Router() 10const serverRouter = express.Router()
9 11
@@ -12,6 +14,8 @@ serverRouter.use('/', serverRedundancyRouter)
12serverRouter.use('/', statsRouter) 14serverRouter.use('/', statsRouter)
13serverRouter.use('/', serverBlocklistRouter) 15serverRouter.use('/', serverBlocklistRouter)
14serverRouter.use('/', contactRouter) 16serverRouter.use('/', contactRouter)
17serverRouter.use('/', logsRouter)
18serverRouter.use('/', debugRouter)
15 19
16// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
17 21
diff --git a/server/controllers/api/server/logs.ts b/server/controllers/api/server/logs.ts
new file mode 100644
index 000000000..e9d1f2efd
--- /dev/null
+++ b/server/controllers/api/server/logs.ts
@@ -0,0 +1,95 @@
1import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
4import { mtimeSortFilesDesc } from '../../../../shared/core-utils/logs/logs'
5import { readdir, readFile } from 'fs-extra'
6import { MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants'
7import { join } from 'path'
8import { getLogsValidator } from '../../../middlewares/validators/logs'
9import { LogLevel } from '../../../../shared/models/server/log-level.type'
10import { CONFIG } from '../../../initializers/config'
11
12const logsRouter = express.Router()
13
14logsRouter.get('/logs',
15 authenticate,
16 ensureUserHasRight(UserRight.MANAGE_LOGS),
17 getLogsValidator,
18 asyncMiddleware(getLogs)
19)
20
21// ---------------------------------------------------------------------------
22
23export {
24 logsRouter
25}
26
27// ---------------------------------------------------------------------------
28
29async function getLogs (req: express.Request, res: express.Response) {
30 const logFiles = await readdir(CONFIG.STORAGE.LOG_DIR)
31 const sortedLogFiles = await mtimeSortFilesDesc(logFiles, CONFIG.STORAGE.LOG_DIR)
32 let currentSize = 0
33
34 const startDate = new Date(req.query.startDate)
35 const endDate = req.query.endDate ? new Date(req.query.endDate) : new Date()
36 const level: LogLevel = req.query.level || 'info'
37
38 let output: string[] = []
39
40 for (const meta of sortedLogFiles) {
41 const path = join(CONFIG.STORAGE.LOG_DIR, meta.file)
42
43 const result = await getOutputFromFile(path, startDate, endDate, level, currentSize)
44 if (!result.output) break
45
46 output = result.output.concat(output)
47 currentSize = result.currentSize
48
49 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS || (result.logTime && result.logTime < startDate.getTime())) break
50 }
51
52 return res.json(output).end()
53}
54
55async function getOutputFromFile (path: string, startDate: Date, endDate: Date, level: LogLevel, currentSize: number) {
56 const startTime = startDate.getTime()
57 const endTime = endDate.getTime()
58 let logTime: number
59
60 const logsLevel: { [ id in LogLevel ]: number } = {
61 debug: 0,
62 info: 1,
63 warn: 2,
64 error: 3
65 }
66
67 const content = await readFile(path)
68 const lines = content.toString().split('\n')
69 const output: any[] = []
70
71 for (let i = lines.length - 1; i >= 0; i--) {
72 const line = lines[ i ]
73 let log: any
74
75 try {
76 log = JSON.parse(line)
77 } catch {
78 // Maybe there a multiple \n at the end of the file
79 continue
80 }
81
82 logTime = new Date(log.timestamp).getTime()
83 if (logTime >= startTime && logTime <= endTime && logsLevel[ log.level ] >= logsLevel[ level ]) {
84 output.push(log)
85
86 currentSize += line.length
87
88 if (currentSize > MAX_LOGS_OUTPUT_CHARACTERS) break
89 } else if (logTime < startTime) {
90 break
91 }
92 }
93
94 return { currentSize, output: output.reverse(), logTime }
95}
diff --git a/server/controllers/api/server/redundancy.ts b/server/controllers/api/server/redundancy.ts
index 4140c4991..f8109070d 100644
--- a/server/controllers/api/server/redundancy.ts
+++ b/server/controllers/api/server/redundancy.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { UserRight } from '../../../../shared/models/users' 2import { UserRight } from '../../../../shared/models/users'
3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' 3import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares'
4import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy' 4import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy'
5import { ServerModel } from '../../../models/server/server'
6import { removeRedundancyOf } from '../../../lib/redundancy' 5import { removeRedundancyOf } from '../../../lib/redundancy'
7import { logger } from '../../../helpers/logger' 6import { logger } from '../../../helpers/logger'
8 7
@@ -23,8 +22,8 @@ export {
23 22
24// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
25 24
26async function updateRedundancy (req: express.Request, res: express.Response, next: express.NextFunction) { 25async function updateRedundancy (req: express.Request, res: express.Response) {
27 const server = res.locals.server as ServerModel 26 const server = res.locals.server
28 27
29 server.redundancyAllowed = req.body.redundancyAllowed 28 server.redundancyAllowed = req.body.redundancyAllowed
30 29
diff --git a/server/controllers/api/server/server-blocklist.ts b/server/controllers/api/server/server-blocklist.ts
index 3cb3a96e2..d165db191 100644
--- a/server/controllers/api/server/server-blocklist.ts
+++ b/server/controllers/api/server/server-blocklist.ts
@@ -18,11 +18,9 @@ import {
18 unblockAccountByServerValidator, 18 unblockAccountByServerValidator,
19 unblockServerByServerValidator 19 unblockServerByServerValidator
20} from '../../../middlewares/validators' 20} from '../../../middlewares/validators'
21import { AccountModel } from '../../../models/account/account'
22import { AccountBlocklistModel } from '../../../models/account/account-blocklist' 21import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
23import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' 22import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
24import { ServerBlocklistModel } from '../../../models/server/server-blocklist' 23import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
25import { ServerModel } from '../../../models/server/server'
26import { UserRight } from '../../../../shared/models/users' 24import { UserRight } from '../../../../shared/models/users'
27 25
28const serverBlocklistRouter = express.Router() 26const serverBlocklistRouter = express.Router()
@@ -91,7 +89,7 @@ async function listBlockedAccounts (req: express.Request, res: express.Response)
91 89
92async function blockAccount (req: express.Request, res: express.Response) { 90async function blockAccount (req: express.Request, res: express.Response) {
93 const serverActor = await getServerActor() 91 const serverActor = await getServerActor()
94 const accountToBlock: AccountModel = res.locals.account 92 const accountToBlock = res.locals.account
95 93
96 await addAccountInBlocklist(serverActor.Account.id, accountToBlock.id) 94 await addAccountInBlocklist(serverActor.Account.id, accountToBlock.id)
97 95
@@ -99,7 +97,7 @@ async function blockAccount (req: express.Request, res: express.Response) {
99} 97}
100 98
101async function unblockAccount (req: express.Request, res: express.Response) { 99async function unblockAccount (req: express.Request, res: express.Response) {
102 const accountBlock: AccountBlocklistModel = res.locals.accountBlock 100 const accountBlock = res.locals.accountBlock
103 101
104 await removeAccountFromBlocklist(accountBlock) 102 await removeAccountFromBlocklist(accountBlock)
105 103
@@ -116,7 +114,7 @@ async function listBlockedServers (req: express.Request, res: express.Response)
116 114
117async function blockServer (req: express.Request, res: express.Response) { 115async function blockServer (req: express.Request, res: express.Response) {
118 const serverActor = await getServerActor() 116 const serverActor = await getServerActor()
119 const serverToBlock: ServerModel = res.locals.server 117 const serverToBlock = res.locals.server
120 118
121 await addServerInBlocklist(serverActor.Account.id, serverToBlock.id) 119 await addServerInBlocklist(serverActor.Account.id, serverToBlock.id)
122 120
@@ -124,7 +122,7 @@ async function blockServer (req: express.Request, res: express.Response) {
124} 122}
125 123
126async function unblockServer (req: express.Request, res: express.Response) { 124async function unblockServer (req: express.Request, res: express.Response) {
127 const serverBlock: ServerBlocklistModel = res.locals.serverBlock 125 const serverBlock = res.locals.serverBlock
128 126
129 await removeServerFromBlocklist(serverBlock) 127 await removeServerFromBlocklist(serverBlock)
130 128
diff --git a/server/controllers/api/server/stats.ts b/server/controllers/api/server/stats.ts
index 89ffd1717..951b98209 100644
--- a/server/controllers/api/server/stats.ts
+++ b/server/controllers/api/server/stats.ts
@@ -6,9 +6,10 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
6import { VideoModel } from '../../../models/video/video' 6import { VideoModel } from '../../../models/video/video'
7import { VideoCommentModel } from '../../../models/video/video-comment' 7import { VideoCommentModel } from '../../../models/video/video-comment'
8import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' 8import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy'
9import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../../initializers/constants' 9import { 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'
12 13
13const statsRouter = express.Router() 14const statsRouter = express.Router()
14 15
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts
index dbe0718d4..0aafba66e 100644
--- a/server/controllers/api/users/index.ts
+++ b/server/controllers/api/users/index.ts
@@ -3,10 +3,10 @@ import * 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 { getFormattedObjects } from '../../../helpers/utils'
6import { CONFIG, RATES_LIMIT, sequelizeTypescript } from '../../../initializers' 6import { RATES_LIMIT, WEBSERVER } from '../../../initializers/constants'
7import { Emailer } from '../../../lib/emailer' 7import { Emailer } from '../../../lib/emailer'
8import { Redis } from '../../../lib/redis' 8import { Redis } from '../../../lib/redis'
9import { createUserAccountAndChannel } from '../../../lib/user' 9import { createUserAccountAndChannelAndPlaylist } from '../../../lib/user'
10import { 10import {
11 asyncMiddleware, 11 asyncMiddleware,
12 asyncRetryTransactionMiddleware, 12 asyncRetryTransactionMiddleware,
@@ -38,23 +38,25 @@ import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../h
38import { meRouter } from './me' 38import { meRouter } from './me'
39import { deleteUserToken } from '../../../lib/oauth-model' 39import { deleteUserToken } from '../../../lib/oauth-model'
40import { myBlocklistRouter } from './my-blocklist' 40import { myBlocklistRouter } from './my-blocklist'
41import { myVideoPlaylistsRouter } from './my-video-playlists'
41import { myVideosHistoryRouter } from './my-history' 42import { myVideosHistoryRouter } from './my-history'
42import { myNotificationsRouter } from './my-notifications' 43import { myNotificationsRouter } from './my-notifications'
43import { Notifier } from '../../../lib/notifier' 44import { Notifier } from '../../../lib/notifier'
44import { mySubscriptionsRouter } from './my-subscriptions' 45import { mySubscriptionsRouter } from './my-subscriptions'
46import { CONFIG } from '../../../initializers/config'
47import { sequelizeTypescript } from '../../../initializers/database'
48import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model'
45 49
46const auditLogger = auditLoggerFactory('users') 50const auditLogger = auditLoggerFactory('users')
47 51
48const loginRateLimiter = new RateLimit({ 52const loginRateLimiter = new RateLimit({
49 windowMs: RATES_LIMIT.LOGIN.WINDOW_MS, 53 windowMs: RATES_LIMIT.LOGIN.WINDOW_MS,
50 max: RATES_LIMIT.LOGIN.MAX, 54 max: RATES_LIMIT.LOGIN.MAX
51 delayMs: 0
52}) 55})
53 56
54const askSendEmailLimiter = new RateLimit({ 57const askSendEmailLimiter = new RateLimit({
55 windowMs: RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, 58 windowMs: RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS,
56 max: RATES_LIMIT.ASK_SEND_EMAIL.MAX, 59 max: RATES_LIMIT.ASK_SEND_EMAIL.MAX
57 delayMs: 0
58}) 60})
59 61
60const usersRouter = express.Router() 62const usersRouter = express.Router()
@@ -62,6 +64,7 @@ usersRouter.use('/', myNotificationsRouter)
62usersRouter.use('/', mySubscriptionsRouter) 64usersRouter.use('/', mySubscriptionsRouter)
63usersRouter.use('/', myBlocklistRouter) 65usersRouter.use('/', myBlocklistRouter)
64usersRouter.use('/', myVideosHistoryRouter) 66usersRouter.use('/', myVideosHistoryRouter)
67usersRouter.use('/', myVideoPlaylistsRouter)
65usersRouter.use('/', meRouter) 68usersRouter.use('/', meRouter)
66 69
67usersRouter.get('/autocomplete', 70usersRouter.get('/autocomplete',
@@ -173,10 +176,11 @@ async function createUser (req: express.Request, res: express.Response) {
173 autoPlayVideo: true, 176 autoPlayVideo: true,
174 role: body.role, 177 role: body.role,
175 videoQuota: body.videoQuota, 178 videoQuota: body.videoQuota,
176 videoQuotaDaily: body.videoQuotaDaily 179 videoQuotaDaily: body.videoQuotaDaily,
180 adminFlags: body.adminFlags || UserAdminFlag.NONE
177 }) 181 })
178 182
179 const { user, account } = await createUserAccountAndChannel(userToCreate) 183 const { user, account } = await createUserAccountAndChannelAndPlaylist(userToCreate)
180 184
181 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) 185 auditLogger.create(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()))
182 logger.info('User %s with its channel and account created.', body.username) 186 logger.info('User %s with its channel and account created.', body.username)
@@ -207,7 +211,7 @@ async function registerUser (req: express.Request, res: express.Response) {
207 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null 211 emailVerified: CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION ? false : null
208 }) 212 })
209 213
210 const { user } = await createUserAccountAndChannel(userToCreate) 214 const { user } = await createUserAccountAndChannelAndPlaylist(userToCreate)
211 215
212 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON())) 216 auditLogger.create(body.username, new UserAuditView(user.toFormattedJSON()))
213 logger.info('User %s with its channel and account registered.', body.username) 217 logger.info('User %s with its channel and account registered.', body.username)
@@ -221,16 +225,16 @@ async function registerUser (req: express.Request, res: express.Response) {
221 return res.type('json').status(204).end() 225 return res.type('json').status(204).end()
222} 226}
223 227
224async function unblockUser (req: express.Request, res: express.Response, next: express.NextFunction) { 228async function unblockUser (req: express.Request, res: express.Response) {
225 const user: UserModel = res.locals.user 229 const user = res.locals.user
226 230
227 await changeUserBlock(res, user, false) 231 await changeUserBlock(res, user, false)
228 232
229 return res.status(204).end() 233 return res.status(204).end()
230} 234}
231 235
232async function blockUser (req: express.Request, res: express.Response, next: express.NextFunction) { 236async function blockUser (req: express.Request, res: express.Response) {
233 const user: UserModel = res.locals.user 237 const user = res.locals.user
234 const reason = req.body.reason 238 const reason = req.body.reason
235 239
236 await changeUserBlock(res, user, true, reason) 240 await changeUserBlock(res, user, true, reason)
@@ -238,24 +242,24 @@ async function blockUser (req: express.Request, res: express.Response, next: exp
238 return res.status(204).end() 242 return res.status(204).end()
239} 243}
240 244
241function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { 245function getUser (req: express.Request, res: express.Response) {
242 return res.json((res.locals.user as UserModel).toFormattedJSON()) 246 return res.json(res.locals.user.toFormattedJSON({ withAdminFlags: true }))
243} 247}
244 248
245async function autocompleteUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 249async function autocompleteUsers (req: express.Request, res: express.Response) {
246 const resultList = await UserModel.autoComplete(req.query.search as string) 250 const resultList = await UserModel.autoComplete(req.query.search as string)
247 251
248 return res.json(resultList) 252 return res.json(resultList)
249} 253}
250 254
251async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { 255async function listUsers (req: express.Request, res: express.Response) {
252 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search) 256 const resultList = await UserModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.search)
253 257
254 return res.json(getFormattedObjects(resultList.data, resultList.total)) 258 return res.json(getFormattedObjects(resultList.data, resultList.total, { withAdminFlags: true }))
255} 259}
256 260
257async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { 261async function removeUser (req: express.Request, res: express.Response) {
258 const user: UserModel = res.locals.user 262 const user = res.locals.user
259 263
260 await user.destroy() 264 await user.destroy()
261 265
@@ -264,42 +268,44 @@ async function removeUser (req: express.Request, res: express.Response, next: ex
264 return res.sendStatus(204) 268 return res.sendStatus(204)
265} 269}
266 270
267async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { 271async function updateUser (req: express.Request, res: express.Response) {
268 const body: UserUpdate = req.body 272 const body: UserUpdate = req.body
269 const userToUpdate = res.locals.user as UserModel 273 const userToUpdate = res.locals.user
270 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON()) 274 const oldUserAuditView = new UserAuditView(userToUpdate.toFormattedJSON())
271 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role 275 const roleChanged = body.role !== undefined && body.role !== userToUpdate.role
272 276
277 if (body.password !== undefined) userToUpdate.password = body.password
273 if (body.email !== undefined) userToUpdate.email = body.email 278 if (body.email !== undefined) userToUpdate.email = body.email
274 if (body.emailVerified !== undefined) userToUpdate.emailVerified = body.emailVerified 279 if (body.emailVerified !== undefined) userToUpdate.emailVerified = body.emailVerified
275 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota 280 if (body.videoQuota !== undefined) userToUpdate.videoQuota = body.videoQuota
276 if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily 281 if (body.videoQuotaDaily !== undefined) userToUpdate.videoQuotaDaily = body.videoQuotaDaily
277 if (body.role !== undefined) userToUpdate.role = body.role 282 if (body.role !== undefined) userToUpdate.role = body.role
283 if (body.adminFlags !== undefined) userToUpdate.adminFlags = body.adminFlags
278 284
279 const user = await userToUpdate.save() 285 const user = await userToUpdate.save()
280 286
281 // Destroy user token to refresh rights 287 // Destroy user token to refresh rights
282 if (roleChanged) await deleteUserToken(userToUpdate.id) 288 if (roleChanged || body.password !== undefined) await deleteUserToken(userToUpdate.id)
283 289
284 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) 290 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView)
285 291
286 // Don't need to send this update to followers, these attributes are not propagated 292 // Don't need to send this update to followers, these attributes are not federated
287 293
288 return res.sendStatus(204) 294 return res.sendStatus(204)
289} 295}
290 296
291async function askResetUserPassword (req: express.Request, res: express.Response, next: express.NextFunction) { 297async function askResetUserPassword (req: express.Request, res: express.Response) {
292 const user = res.locals.user as UserModel 298 const user = res.locals.user
293 299
294 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id) 300 const verificationString = await Redis.Instance.setResetPasswordVerificationString(user.id)
295 const url = CONFIG.WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString 301 const url = WEBSERVER.URL + '/reset-password?userId=' + user.id + '&verificationString=' + verificationString
296 await Emailer.Instance.addForgetPasswordEmailJob(user.email, url) 302 await Emailer.Instance.addPasswordResetEmailJob(user.email, url)
297 303
298 return res.status(204).end() 304 return res.status(204).end()
299} 305}
300 306
301async function resetUserPassword (req: express.Request, res: express.Response, next: express.NextFunction) { 307async function resetUserPassword (req: express.Request, res: express.Response) {
302 const user = res.locals.user as UserModel 308 const user = res.locals.user
303 user.password = req.body.password 309 user.password = req.body.password
304 310
305 await user.save() 311 await user.save()
@@ -309,21 +315,21 @@ async function resetUserPassword (req: express.Request, res: express.Response, n
309 315
310async function sendVerifyUserEmail (user: UserModel) { 316async function sendVerifyUserEmail (user: UserModel) {
311 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) 317 const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id)
312 const url = CONFIG.WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString 318 const url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString
313 await Emailer.Instance.addVerifyEmailJob(user.email, url) 319 await Emailer.Instance.addVerifyEmailJob(user.email, url)
314 return 320 return
315} 321}
316 322
317async function askSendVerifyUserEmail (req: express.Request, res: express.Response, next: express.NextFunction) { 323async function askSendVerifyUserEmail (req: express.Request, res: express.Response) {
318 const user = res.locals.user as UserModel 324 const user = res.locals.user
319 325
320 await sendVerifyUserEmail(user) 326 await sendVerifyUserEmail(user)
321 327
322 return res.status(204).end() 328 return res.status(204).end()
323} 329}
324 330
325async function verifyUserEmail (req: express.Request, res: express.Response, next: express.NextFunction) { 331async function verifyUserEmail (req: express.Request, res: express.Response) {
326 const user = res.locals.user as UserModel 332 const user = res.locals.user
327 user.emailVerified = true 333 user.emailVerified = true
328 334
329 await user.save() 335 await user.save()
@@ -331,7 +337,7 @@ async function verifyUserEmail (req: express.Request, res: express.Response, nex
331 return res.status(204).end() 337 return res.status(204).end()
332} 338}
333 339
334function success (req: express.Request, res: express.Response, next: express.NextFunction) { 340function success (req: express.Request, res: express.Response) {
335 res.end() 341 res.end()
336} 342}
337 343
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts
index 94a2b8732..ddb239e7b 100644
--- a/server/controllers/api/users/me.ts
+++ b/server/controllers/api/users/me.ts
@@ -2,7 +2,7 @@ import * as express from 'express'
2import 'multer' 2import 'multer'
3import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' 3import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers' 5import { MIMETYPES } from '../../../initializers/constants'
6import { sendUpdateActor } from '../../../lib/activitypub/send' 6import { sendUpdateActor } from '../../../lib/activitypub/send'
7import { 7import {
8 asyncMiddleware, 8 asyncMiddleware,
@@ -26,6 +26,8 @@ import { updateActorAvatarFile } from '../../../lib/avatar'
26import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' 26import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger'
27import { VideoImportModel } from '../../../models/video/video-import' 27import { VideoImportModel } from '../../../models/video/video-import'
28import { AccountModel } from '../../../models/account/account' 28import { AccountModel } from '../../../models/account/account'
29import { CONFIG } from '../../../initializers/config'
30import { sequelizeTypescript } from '../../../initializers/database'
29 31
30const auditLogger = auditLoggerFactory('users-me') 32const auditLogger = auditLoggerFactory('users-me')
31 33
@@ -93,8 +95,8 @@ export {
93 95
94// --------------------------------------------------------------------------- 96// ---------------------------------------------------------------------------
95 97
96async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 98async function getUserVideos (req: express.Request, res: express.Response) {
97 const user = res.locals.oauth.token.User as UserModel 99 const user = res.locals.oauth.token.User
98 const resultList = await VideoModel.listUserVideosForApi( 100 const resultList = await VideoModel.listUserVideosForApi(
99 user.Account.id, 101 user.Account.id,
100 req.query.start as number, 102 req.query.start as number,
@@ -111,8 +113,8 @@ async function getUserVideos (req: express.Request, res: express.Response, next:
111 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes })) 113 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
112} 114}
113 115
114async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) { 116async function getUserVideoImports (req: express.Request, res: express.Response) {
115 const user = res.locals.oauth.token.User as UserModel 117 const user = res.locals.oauth.token.User
116 const resultList = await VideoImportModel.listUserVideoImportsForApi( 118 const resultList = await VideoImportModel.listUserVideoImportsForApi(
117 user.id, 119 user.id,
118 req.query.start as number, 120 req.query.start as number,
@@ -123,14 +125,14 @@ async function getUserVideoImports (req: express.Request, res: express.Response,
123 return res.json(getFormattedObjects(resultList.data, resultList.total)) 125 return res.json(getFormattedObjects(resultList.data, resultList.total))
124} 126}
125 127
126async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { 128async function getUserInformation (req: express.Request, res: express.Response) {
127 // We did not load channels in res.locals.user 129 // We did not load channels in res.locals.user
128 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) 130 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
129 131
130 return res.json(user.toFormattedJSON()) 132 return res.json(user.toFormattedJSON({}))
131} 133}
132 134
133async function getUserVideoQuotaUsed (req: express.Request, res: express.Response, next: express.NextFunction) { 135async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) {
134 // We did not load channels in res.locals.user 136 // We did not load channels in res.locals.user
135 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) 137 const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username)
136 const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user) 138 const videoQuotaUsed = await UserModel.getOriginalVideoFileTotalFromUser(user)
@@ -143,7 +145,7 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons
143 return res.json(data) 145 return res.json(data)
144} 146}
145 147
146async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { 148async function getUserVideoRating (req: express.Request, res: express.Response) {
147 const videoId = res.locals.video.id 149 const videoId = res.locals.video.id
148 const accountId = +res.locals.oauth.token.User.Account.id 150 const accountId = +res.locals.oauth.token.User.Account.id
149 151
@@ -158,20 +160,20 @@ async function getUserVideoRating (req: express.Request, res: express.Response,
158} 160}
159 161
160async function deleteMe (req: express.Request, res: express.Response) { 162async function deleteMe (req: express.Request, res: express.Response) {
161 const user: UserModel = res.locals.oauth.token.User 163 const user = res.locals.oauth.token.User
162 164
163 await user.destroy() 165 await user.destroy()
164 166
165 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON())) 167 auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})))
166 168
167 return res.sendStatus(204) 169 return res.sendStatus(204)
168} 170}
169 171
170async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) { 172async function updateMe (req: express.Request, res: express.Response) {
171 const body: UserUpdateMe = req.body 173 const body: UserUpdateMe = req.body
172 174
173 const user: UserModel = res.locals.oauth.token.user 175 const user = res.locals.oauth.token.user
174 const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) 176 const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
175 177
176 if (body.password !== undefined) user.password = body.password 178 if (body.password !== undefined) user.password = body.password
177 if (body.email !== undefined) user.email = body.email 179 if (body.email !== undefined) user.email = body.email
@@ -191,7 +193,7 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
191 193
192 await sendUpdateActor(userAccount, t) 194 await sendUpdateActor(userAccount, t)
193 195
194 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) 196 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
195 }) 197 })
196 198
197 return res.sendStatus(204) 199 return res.sendStatus(204)
@@ -199,14 +201,14 @@ async function updateMe (req: express.Request, res: express.Response, next: expr
199 201
200async function updateMyAvatar (req: express.Request, res: express.Response) { 202async function updateMyAvatar (req: express.Request, res: express.Response) {
201 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 203 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
202 const user: UserModel = res.locals.oauth.token.user 204 const user = res.locals.oauth.token.user
203 const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) 205 const oldUserAuditView = new UserAuditView(user.toFormattedJSON({}))
204 206
205 const userAccount = await AccountModel.load(user.Account.id) 207 const userAccount = await AccountModel.load(user.Account.id)
206 208
207 const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) 209 const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount)
208 210
209 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON()), oldUserAuditView) 211 auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView)
210 212
211 return res.json({ avatar: avatar.toFormattedJSON() }) 213 return res.json({ avatar: avatar.toFormattedJSON() })
212} 214}
diff --git a/server/controllers/api/users/my-blocklist.ts b/server/controllers/api/users/my-blocklist.ts
index 9575eab46..713c16022 100644
--- a/server/controllers/api/users/my-blocklist.ts
+++ b/server/controllers/api/users/my-blocklist.ts
@@ -17,12 +17,9 @@ import {
17 serversBlocklistSortValidator, 17 serversBlocklistSortValidator,
18 unblockServerByAccountValidator 18 unblockServerByAccountValidator
19} from '../../../middlewares/validators' 19} from '../../../middlewares/validators'
20import { UserModel } from '../../../models/account/user'
21import { AccountModel } from '../../../models/account/account'
22import { AccountBlocklistModel } from '../../../models/account/account-blocklist' 20import { AccountBlocklistModel } from '../../../models/account/account-blocklist'
23import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' 21import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist'
24import { ServerBlocklistModel } from '../../../models/server/server-blocklist' 22import { ServerBlocklistModel } from '../../../models/server/server-blocklist'
25import { ServerModel } from '../../../models/server/server'
26 23
27const myBlocklistRouter = express.Router() 24const myBlocklistRouter = express.Router()
28 25
@@ -75,7 +72,7 @@ export {
75// --------------------------------------------------------------------------- 72// ---------------------------------------------------------------------------
76 73
77async function listBlockedAccounts (req: express.Request, res: express.Response) { 74async function listBlockedAccounts (req: express.Request, res: express.Response) {
78 const user: UserModel = res.locals.oauth.token.User 75 const user = res.locals.oauth.token.User
79 76
80 const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort) 77 const resultList = await AccountBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
81 78
@@ -83,8 +80,8 @@ async function listBlockedAccounts (req: express.Request, res: express.Response)
83} 80}
84 81
85async function blockAccount (req: express.Request, res: express.Response) { 82async function blockAccount (req: express.Request, res: express.Response) {
86 const user: UserModel = res.locals.oauth.token.User 83 const user = res.locals.oauth.token.User
87 const accountToBlock: AccountModel = res.locals.account 84 const accountToBlock = res.locals.account
88 85
89 await addAccountInBlocklist(user.Account.id, accountToBlock.id) 86 await addAccountInBlocklist(user.Account.id, accountToBlock.id)
90 87
@@ -92,7 +89,7 @@ async function blockAccount (req: express.Request, res: express.Response) {
92} 89}
93 90
94async function unblockAccount (req: express.Request, res: express.Response) { 91async function unblockAccount (req: express.Request, res: express.Response) {
95 const accountBlock: AccountBlocklistModel = res.locals.accountBlock 92 const accountBlock = res.locals.accountBlock
96 93
97 await removeAccountFromBlocklist(accountBlock) 94 await removeAccountFromBlocklist(accountBlock)
98 95
@@ -100,7 +97,7 @@ async function unblockAccount (req: express.Request, res: express.Response) {
100} 97}
101 98
102async function listBlockedServers (req: express.Request, res: express.Response) { 99async function listBlockedServers (req: express.Request, res: express.Response) {
103 const user: UserModel = res.locals.oauth.token.User 100 const user = res.locals.oauth.token.User
104 101
105 const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort) 102 const resultList = await ServerBlocklistModel.listForApi(user.Account.id, req.query.start, req.query.count, req.query.sort)
106 103
@@ -108,8 +105,8 @@ async function listBlockedServers (req: express.Request, res: express.Response)
108} 105}
109 106
110async function blockServer (req: express.Request, res: express.Response) { 107async function blockServer (req: express.Request, res: express.Response) {
111 const user: UserModel = res.locals.oauth.token.User 108 const user = res.locals.oauth.token.User
112 const serverToBlock: ServerModel = res.locals.server 109 const serverToBlock = res.locals.server
113 110
114 await addServerInBlocklist(user.Account.id, serverToBlock.id) 111 await addServerInBlocklist(user.Account.id, serverToBlock.id)
115 112
@@ -117,7 +114,7 @@ async function blockServer (req: express.Request, res: express.Response) {
117} 114}
118 115
119async function unblockServer (req: express.Request, res: express.Response) { 116async function unblockServer (req: express.Request, res: express.Response) {
120 const serverBlock: ServerBlocklistModel = res.locals.serverBlock 117 const serverBlock = res.locals.serverBlock
121 118
122 await removeServerFromBlocklist(serverBlock) 119 await removeServerFromBlocklist(serverBlock)
123 120
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts
index 6cd782c47..7025c0ff1 100644
--- a/server/controllers/api/users/my-history.ts
+++ b/server/controllers/api/users/my-history.ts
@@ -36,7 +36,7 @@ export {
36// --------------------------------------------------------------------------- 36// ---------------------------------------------------------------------------
37 37
38async function listMyVideosHistory (req: express.Request, res: express.Response) { 38async function listMyVideosHistory (req: express.Request, res: express.Response) {
39 const user: UserModel = res.locals.oauth.token.User 39 const user = res.locals.oauth.token.User
40 40
41 const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count) 41 const resultList = await UserVideoHistoryModel.listForApi(user, req.query.start, req.query.count)
42 42
@@ -44,11 +44,11 @@ async function listMyVideosHistory (req: express.Request, res: express.Response)
44} 44}
45 45
46async function removeUserHistory (req: express.Request, res: express.Response) { 46async function removeUserHistory (req: express.Request, res: express.Response) {
47 const user: UserModel = res.locals.oauth.token.User 47 const user = res.locals.oauth.token.User
48 const beforeDate = req.body.beforeDate || null 48 const beforeDate = req.body.beforeDate || null
49 49
50 await sequelizeTypescript.transaction(t => { 50 await sequelizeTypescript.transaction(t => {
51 return UserVideoHistoryModel.removeHistoryBefore(user, beforeDate, t) 51 return UserVideoHistoryModel.removeUserHistoryBefore(user, beforeDate, t)
52 }) 52 })
53 53
54 // Do not send the delete to other instances, we delete OUR copy of this video abuse 54 // Do not send the delete to other instances, we delete OUR copy of this video abuse
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts
index 76cf97587..f146284e4 100644
--- a/server/controllers/api/users/my-notifications.ts
+++ b/server/controllers/api/users/my-notifications.ts
@@ -9,7 +9,6 @@ import {
9 setDefaultSort, 9 setDefaultSort,
10 userNotificationsSortValidator 10 userNotificationsSortValidator
11} from '../../../middlewares' 11} from '../../../middlewares'
12import { UserModel } from '../../../models/account/user'
13import { getFormattedObjects } from '../../../helpers/utils' 12import { getFormattedObjects } from '../../../helpers/utils'
14import { UserNotificationModel } from '../../../models/account/user-notification' 13import { UserNotificationModel } from '../../../models/account/user-notification'
15import { meRouter } from './me' 14import { meRouter } from './me'
@@ -57,8 +56,8 @@ export {
57// --------------------------------------------------------------------------- 56// ---------------------------------------------------------------------------
58 57
59async function updateNotificationSettings (req: express.Request, res: express.Response) { 58async function updateNotificationSettings (req: express.Request, res: express.Response) {
60 const user: UserModel = res.locals.oauth.token.User 59 const user = res.locals.oauth.token.User
61 const body = req.body 60 const body = req.body as UserNotificationSetting
62 61
63 const query = { 62 const query = {
64 where: { 63 where: {
@@ -70,12 +69,14 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
70 newVideoFromSubscription: body.newVideoFromSubscription, 69 newVideoFromSubscription: body.newVideoFromSubscription,
71 newCommentOnMyVideo: body.newCommentOnMyVideo, 70 newCommentOnMyVideo: body.newCommentOnMyVideo,
72 videoAbuseAsModerator: body.videoAbuseAsModerator, 71 videoAbuseAsModerator: body.videoAbuseAsModerator,
72 videoAutoBlacklistAsModerator: body.videoAutoBlacklistAsModerator,
73 blacklistOnMyVideo: body.blacklistOnMyVideo, 73 blacklistOnMyVideo: body.blacklistOnMyVideo,
74 myVideoPublished: body.myVideoPublished, 74 myVideoPublished: body.myVideoPublished,
75 myVideoImportFinished: body.myVideoImportFinished, 75 myVideoImportFinished: body.myVideoImportFinished,
76 newFollow: body.newFollow, 76 newFollow: body.newFollow,
77 newUserRegistration: body.newUserRegistration, 77 newUserRegistration: body.newUserRegistration,
78 commentMention: body.commentMention 78 commentMention: body.commentMention,
79 newInstanceFollower: body.newInstanceFollower
79 } 80 }
80 81
81 await UserNotificationSettingModel.update(values, query) 82 await UserNotificationSettingModel.update(values, query)
@@ -84,7 +85,7 @@ async function updateNotificationSettings (req: express.Request, res: express.Re
84} 85}
85 86
86async function listUserNotifications (req: express.Request, res: express.Response) { 87async function listUserNotifications (req: express.Request, res: express.Response) {
87 const user: UserModel = res.locals.oauth.token.User 88 const user = res.locals.oauth.token.User
88 89
89 const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread) 90 const resultList = await UserNotificationModel.listForApi(user.id, req.query.start, req.query.count, req.query.sort, req.query.unread)
90 91
@@ -92,7 +93,7 @@ async function listUserNotifications (req: express.Request, res: express.Respons
92} 93}
93 94
94async function markAsReadUserNotifications (req: express.Request, res: express.Response) { 95async function markAsReadUserNotifications (req: express.Request, res: express.Response) {
95 const user: UserModel = res.locals.oauth.token.User 96 const user = res.locals.oauth.token.User
96 97
97 await UserNotificationModel.markAsRead(user.id, req.body.ids) 98 await UserNotificationModel.markAsRead(user.id, req.body.ids)
98 99
@@ -100,7 +101,7 @@ async function markAsReadUserNotifications (req: express.Request, res: express.R
100} 101}
101 102
102async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) { 103async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) {
103 const user: UserModel = res.locals.oauth.token.User 104 const user = res.locals.oauth.token.User
104 105
105 await UserNotificationModel.markAllAsRead(user.id) 106 await UserNotificationModel.markAllAsRead(user.id)
106 107
diff --git a/server/controllers/api/users/my-subscriptions.ts b/server/controllers/api/users/my-subscriptions.ts
index accca6d52..c52df3154 100644
--- a/server/controllers/api/users/my-subscriptions.ts
+++ b/server/controllers/api/users/my-subscriptions.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import 'multer' 2import 'multer'
3import { getFormattedObjects } from '../../../helpers/utils' 3import { getFormattedObjects } from '../../../helpers/utils'
4import { CONFIG, sequelizeTypescript } from '../../../initializers' 4import { WEBSERVER } from '../../../initializers/constants'
5import { 5import {
6 asyncMiddleware, 6 asyncMiddleware,
7 asyncRetryTransactionMiddleware, 7 asyncRetryTransactionMiddleware,
@@ -14,13 +14,13 @@ import {
14 userSubscriptionGetValidator 14 userSubscriptionGetValidator
15} from '../../../middlewares' 15} from '../../../middlewares'
16import { areSubscriptionsExistValidator, userSubscriptionsSortValidator, videosSortValidator } from '../../../middlewares/validators' 16import { areSubscriptionsExistValidator, userSubscriptionsSortValidator, videosSortValidator } from '../../../middlewares/validators'
17import { UserModel } from '../../../models/account/user'
18import { VideoModel } from '../../../models/video/video' 17import { VideoModel } from '../../../models/video/video'
19import { buildNSFWFilter } from '../../../helpers/express-utils' 18import { buildNSFWFilter } from '../../../helpers/express-utils'
20import { VideoFilter } from '../../../../shared/models/videos/video-query.type' 19import { VideoFilter } from '../../../../shared/models/videos/video-query.type'
21import { ActorFollowModel } from '../../../models/activitypub/actor-follow' 20import { ActorFollowModel } from '../../../models/activitypub/actor-follow'
22import { JobQueue } from '../../../lib/job-queue' 21import { JobQueue } from '../../../lib/job-queue'
23import { logger } from '../../../helpers/logger' 22import { logger } from '../../../helpers/logger'
23import { sequelizeTypescript } from '../../../initializers/database'
24 24
25const mySubscriptionsRouter = express.Router() 25const mySubscriptionsRouter = express.Router()
26 26
@@ -77,11 +77,11 @@ export {
77 77
78async function areSubscriptionsExist (req: express.Request, res: express.Response) { 78async function areSubscriptionsExist (req: express.Request, res: express.Response) {
79 const uris = req.query.uris as string[] 79 const uris = req.query.uris as string[]
80 const user = res.locals.oauth.token.User as UserModel 80 const user = res.locals.oauth.token.User
81 81
82 const handles = uris.map(u => { 82 const handles = uris.map(u => {
83 let [ name, host ] = u.split('@') 83 let [ name, host ] = u.split('@')
84 if (host === CONFIG.WEBSERVER.HOST) host = null 84 if (host === WEBSERVER.HOST) host = null
85 85
86 return { name, host, uri: u } 86 return { name, host, uri: u }
87 }) 87 })
@@ -107,7 +107,7 @@ async function areSubscriptionsExist (req: express.Request, res: express.Respons
107} 107}
108 108
109async function addUserSubscription (req: express.Request, res: express.Response) { 109async function addUserSubscription (req: express.Request, res: express.Response) {
110 const user = res.locals.oauth.token.User as UserModel 110 const user = res.locals.oauth.token.User
111 const [ name, host ] = req.body.uri.split('@') 111 const [ name, host ] = req.body.uri.split('@')
112 112
113 const payload = { 113 const payload = {
@@ -123,13 +123,13 @@ async function addUserSubscription (req: express.Request, res: express.Response)
123} 123}
124 124
125function getUserSubscription (req: express.Request, res: express.Response) { 125function getUserSubscription (req: express.Request, res: express.Response) {
126 const subscription: ActorFollowModel = res.locals.subscription 126 const subscription = res.locals.subscription
127 127
128 return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON()) 128 return res.json(subscription.ActorFollowing.VideoChannel.toFormattedJSON())
129} 129}
130 130
131async function deleteUserSubscription (req: express.Request, res: express.Response) { 131async function deleteUserSubscription (req: express.Request, res: express.Response) {
132 const subscription: ActorFollowModel = res.locals.subscription 132 const subscription = res.locals.subscription
133 133
134 await sequelizeTypescript.transaction(async t => { 134 await sequelizeTypescript.transaction(async t => {
135 return subscription.destroy({ transaction: t }) 135 return subscription.destroy({ transaction: t })
@@ -139,7 +139,7 @@ async function deleteUserSubscription (req: express.Request, res: express.Respon
139} 139}
140 140
141async function getUserSubscriptions (req: express.Request, res: express.Response) { 141async function getUserSubscriptions (req: express.Request, res: express.Response) {
142 const user = res.locals.oauth.token.User as UserModel 142 const user = res.locals.oauth.token.User
143 const actorId = user.Account.Actor.id 143 const actorId = user.Account.Actor.id
144 144
145 const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort) 145 const resultList = await ActorFollowModel.listSubscriptionsForApi(actorId, req.query.start, req.query.count, req.query.sort)
@@ -147,8 +147,8 @@ async function getUserSubscriptions (req: express.Request, res: express.Response
147 return res.json(getFormattedObjects(resultList.data, resultList.total)) 147 return res.json(getFormattedObjects(resultList.data, resultList.total))
148} 148}
149 149
150async function getUserSubscriptionVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 150async function getUserSubscriptionVideos (req: express.Request, res: express.Response) {
151 const user = res.locals.oauth.token.User as UserModel 151 const user = res.locals.oauth.token.User
152 const resultList = await VideoModel.listForApi({ 152 const resultList = await VideoModel.listForApi({
153 start: req.query.start, 153 start: req.query.start,
154 count: req.query.count, 154 count: req.query.count,
diff --git a/server/controllers/api/users/my-video-playlists.ts b/server/controllers/api/users/my-video-playlists.ts
new file mode 100644
index 000000000..15e92f4f3
--- /dev/null
+++ b/server/controllers/api/users/my-video-playlists.ts
@@ -0,0 +1,46 @@
1import * as express from 'express'
2import { asyncMiddleware, authenticate } from '../../../middlewares'
3import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists'
4import { VideoPlaylistModel } from '../../../models/video/video-playlist'
5import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model'
6
7const myVideoPlaylistsRouter = express.Router()
8
9myVideoPlaylistsRouter.get('/me/video-playlists/videos-exist',
10 authenticate,
11 doVideosInPlaylistExistValidator,
12 asyncMiddleware(doVideosInPlaylistExist)
13)
14
15// ---------------------------------------------------------------------------
16
17export {
18 myVideoPlaylistsRouter
19}
20
21// ---------------------------------------------------------------------------
22
23async function doVideosInPlaylistExist (req: express.Request, res: express.Response) {
24 const videoIds = req.query.videoIds.map(i => parseInt(i + '', 10))
25 const user = res.locals.oauth.token.User
26
27 const results = await VideoPlaylistModel.listPlaylistIdsOf(user.Account.id, videoIds)
28
29 const existObject: VideoExistInPlaylist = {}
30
31 for (const videoId of videoIds) {
32 existObject[videoId] = []
33 }
34
35 for (const result of results) {
36 for (const element of result.VideoPlaylistElements) {
37 existObject[element.videoId].push({
38 playlistId: result.id,
39 startTimestamp: element.startTimestamp,
40 stopTimestamp: element.stopTimestamp
41 })
42 }
43 }
44
45 return res.json(existObject)
46}
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts
index db7602139..3d6dbfe70 100644
--- a/server/controllers/api/video-channel.ts
+++ b/server/controllers/api/video-channel.ts
@@ -12,7 +12,8 @@ import {
12 videoChannelsAddValidator, 12 videoChannelsAddValidator,
13 videoChannelsRemoveValidator, 13 videoChannelsRemoveValidator,
14 videoChannelsSortValidator, 14 videoChannelsSortValidator,
15 videoChannelsUpdateValidator 15 videoChannelsUpdateValidator,
16 videoPlaylistsSortValidator
16} from '../../middlewares' 17} from '../../middlewares'
17import { VideoChannelModel } from '../../models/video/video-channel' 18import { VideoChannelModel } from '../../models/video/video-channel'
18import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' 19import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators'
@@ -22,15 +23,18 @@ import { createVideoChannel } from '../../lib/video-channel'
22import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' 23import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
23import { setAsyncActorKeys } from '../../lib/activitypub' 24import { setAsyncActorKeys } from '../../lib/activitypub'
24import { AccountModel } from '../../models/account/account' 25import { AccountModel } from '../../models/account/account'
25import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers' 26import { MIMETYPES } from '../../initializers/constants'
26import { logger } from '../../helpers/logger' 27import { logger } from '../../helpers/logger'
27import { VideoModel } from '../../models/video/video' 28import { VideoModel } from '../../models/video/video'
28import { updateAvatarValidator } from '../../middlewares/validators/avatar' 29import { updateAvatarValidator } from '../../middlewares/validators/avatar'
29import { updateActorAvatarFile } from '../../lib/avatar' 30import { updateActorAvatarFile } from '../../lib/avatar'
30import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' 31import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger'
31import { resetSequelizeInstance } from '../../helpers/database-utils' 32import { resetSequelizeInstance } from '../../helpers/database-utils'
32import { UserModel } from '../../models/account/user'
33import { JobQueue } from '../../lib/job-queue' 33import { JobQueue } from '../../lib/job-queue'
34import { VideoPlaylistModel } from '../../models/video/video-playlist'
35import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists'
36import { CONFIG } from '../../initializers/config'
37import { sequelizeTypescript } from '../../initializers/database'
34 38
35const auditLogger = auditLoggerFactory('channels') 39const auditLogger = auditLoggerFactory('channels')
36const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) 40const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR })
@@ -77,6 +81,16 @@ videoChannelRouter.get('/:nameWithHost',
77 asyncMiddleware(getVideoChannel) 81 asyncMiddleware(getVideoChannel)
78) 82)
79 83
84videoChannelRouter.get('/:nameWithHost/video-playlists',
85 asyncMiddleware(videoChannelsNameWithHostValidator),
86 paginationValidator,
87 videoPlaylistsSortValidator,
88 setDefaultSort,
89 setDefaultPagination,
90 commonVideoPlaylistFiltersValidator,
91 asyncMiddleware(listVideoChannelPlaylists)
92)
93
80videoChannelRouter.get('/:nameWithHost/videos', 94videoChannelRouter.get('/:nameWithHost/videos',
81 asyncMiddleware(videoChannelsNameWithHostValidator), 95 asyncMiddleware(videoChannelsNameWithHostValidator),
82 paginationValidator, 96 paginationValidator,
@@ -96,16 +110,16 @@ export {
96 110
97// --------------------------------------------------------------------------- 111// ---------------------------------------------------------------------------
98 112
99async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { 113async function listVideoChannels (req: express.Request, res: express.Response) {
100 const serverActor = await getServerActor() 114 const serverActor = await getServerActor()
101 const resultList = await VideoChannelModel.listForApi(serverActor.id, req.query.start, req.query.count, req.query.sort) 115 const resultList = await VideoChannelModel.listForApi(serverActor.id, req.query.start, req.query.count, req.query.sort)
102 116
103 return res.json(getFormattedObjects(resultList.data, resultList.total)) 117 return res.json(getFormattedObjects(resultList.data, resultList.total))
104} 118}
105 119
106async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { 120async function updateVideoChannelAvatar (req: express.Request, res: express.Response) {
107 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] 121 const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ]
108 const videoChannel = res.locals.videoChannel as VideoChannelModel 122 const videoChannel = res.locals.videoChannel
109 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON()) 123 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannel.toFormattedJSON())
110 124
111 const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel) 125 const avatar = await updateActorAvatarFile(avatarPhysicalFile, videoChannel)
@@ -123,7 +137,7 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
123 const videoChannelInfo: VideoChannelCreate = req.body 137 const videoChannelInfo: VideoChannelCreate = req.body
124 138
125 const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => { 139 const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => {
126 const account = await AccountModel.load((res.locals.oauth.token.User as UserModel).Account.id, t) 140 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
127 141
128 return createVideoChannel(videoChannelInfo, account, t) 142 return createVideoChannel(videoChannelInfo, account, t)
129 }) 143 })
@@ -143,7 +157,7 @@ async function addVideoChannel (req: express.Request, res: express.Response) {
143} 157}
144 158
145async function updateVideoChannel (req: express.Request, res: express.Response) { 159async function updateVideoChannel (req: express.Request, res: express.Response) {
146 const videoChannelInstance = res.locals.videoChannel as VideoChannelModel 160 const videoChannelInstance = res.locals.videoChannel
147 const videoChannelFieldsSave = videoChannelInstance.toJSON() 161 const videoChannelFieldsSave = videoChannelInstance.toJSON()
148 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()) 162 const oldVideoChannelAuditKeys = new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())
149 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate 163 const videoChannelInfoToUpdate = req.body as VideoChannelUpdate
@@ -183,9 +197,11 @@ async function updateVideoChannel (req: express.Request, res: express.Response)
183} 197}
184 198
185async function removeVideoChannel (req: express.Request, res: express.Response) { 199async function removeVideoChannel (req: express.Request, res: express.Response) {
186 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel 200 const videoChannelInstance = res.locals.videoChannel
187 201
188 await sequelizeTypescript.transaction(async t => { 202 await sequelizeTypescript.transaction(async t => {
203 await VideoPlaylistModel.resetPlaylistsOfChannel(videoChannelInstance.id, t)
204
189 await videoChannelInstance.destroy({ transaction: t }) 205 await videoChannelInstance.destroy({ transaction: t })
190 206
191 auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON())) 207 auditLogger.delete(getAuditIdFromRes(res), new VideoChannelAuditView(videoChannelInstance.toFormattedJSON()))
@@ -195,7 +211,7 @@ async function removeVideoChannel (req: express.Request, res: express.Response)
195 return res.type('json').status(204).end() 211 return res.type('json').status(204).end()
196} 212}
197 213
198async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { 214async function getVideoChannel (req: express.Request, res: express.Response) {
199 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id) 215 const videoChannelWithVideos = await VideoChannelModel.loadAndPopulateAccountAndVideos(res.locals.videoChannel.id)
200 216
201 if (videoChannelWithVideos.isOutdated()) { 217 if (videoChannelWithVideos.isOutdated()) {
@@ -206,8 +222,23 @@ async function getVideoChannel (req: express.Request, res: express.Response, nex
206 return res.json(videoChannelWithVideos.toFormattedJSON()) 222 return res.json(videoChannelWithVideos.toFormattedJSON())
207} 223}
208 224
209async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) { 225async function listVideoChannelPlaylists (req: express.Request, res: express.Response) {
210 const videoChannelInstance: VideoChannelModel = res.locals.videoChannel 226 const serverActor = await getServerActor()
227
228 const resultList = await VideoPlaylistModel.listForApi({
229 followerActorId: serverActor.id,
230 start: req.query.start,
231 count: req.query.count,
232 sort: req.query.sort,
233 videoChannelId: res.locals.videoChannel.id,
234 type: req.query.playlistType
235 })
236
237 return res.json(getFormattedObjects(resultList.data, resultList.total))
238}
239
240async function listVideoChannelVideos (req: express.Request, res: express.Response) {
241 const videoChannelInstance = res.locals.videoChannel
211 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined 242 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
212 243
213 const resultList = await VideoModel.listForApi({ 244 const resultList = await VideoModel.listForApi({
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts
new file mode 100644
index 000000000..a17136401
--- /dev/null
+++ b/server/controllers/api/video-playlist.ts
@@ -0,0 +1,445 @@
1import * as express from 'express'
2import { getFormattedObjects, getServerActor } from '../../helpers/utils'
3import {
4 asyncMiddleware,
5 asyncRetryTransactionMiddleware,
6 authenticate,
7 commonVideosFiltersValidator,
8 optionalAuthenticate,
9 paginationValidator,
10 setDefaultPagination,
11 setDefaultSort
12} from '../../middlewares'
13import { videoPlaylistsSortValidator } from '../../middlewares/validators'
14import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils'
15import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants'
16import { logger } from '../../helpers/logger'
17import { resetSequelizeInstance } from '../../helpers/database-utils'
18import { VideoPlaylistModel } from '../../models/video/video-playlist'
19import {
20 commonVideoPlaylistFiltersValidator,
21 videoPlaylistsAddValidator,
22 videoPlaylistsAddVideoValidator,
23 videoPlaylistsDeleteValidator,
24 videoPlaylistsGetValidator,
25 videoPlaylistsReorderVideosValidator,
26 videoPlaylistsUpdateOrRemoveVideoValidator,
27 videoPlaylistsUpdateValidator
28} from '../../middlewares/validators/videos/video-playlists'
29import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model'
30import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model'
31import { join } from 'path'
32import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send'
33import { getVideoPlaylistActivityPubUrl, getVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url'
34import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model'
35import { VideoModel } from '../../models/video/video'
36import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element'
37import { VideoPlaylistElementCreate } from '../../../shared/models/videos/playlist/video-playlist-element-create.model'
38import { VideoPlaylistElementUpdate } from '../../../shared/models/videos/playlist/video-playlist-element-update.model'
39import { AccountModel } from '../../models/account/account'
40import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/video-playlist-reorder.model'
41import { JobQueue } from '../../lib/job-queue'
42import { CONFIG } from '../../initializers/config'
43import { sequelizeTypescript } from '../../initializers/database'
44import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail'
45
46const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR })
47
48const videoPlaylistRouter = express.Router()
49
50videoPlaylistRouter.get('/privacies', listVideoPlaylistPrivacies)
51
52videoPlaylistRouter.get('/',
53 paginationValidator,
54 videoPlaylistsSortValidator,
55 setDefaultSort,
56 setDefaultPagination,
57 commonVideoPlaylistFiltersValidator,
58 asyncMiddleware(listVideoPlaylists)
59)
60
61videoPlaylistRouter.get('/:playlistId',
62 asyncMiddleware(videoPlaylistsGetValidator),
63 getVideoPlaylist
64)
65
66videoPlaylistRouter.post('/',
67 authenticate,
68 reqThumbnailFile,
69 asyncMiddleware(videoPlaylistsAddValidator),
70 asyncRetryTransactionMiddleware(addVideoPlaylist)
71)
72
73videoPlaylistRouter.put('/:playlistId',
74 authenticate,
75 reqThumbnailFile,
76 asyncMiddleware(videoPlaylistsUpdateValidator),
77 asyncRetryTransactionMiddleware(updateVideoPlaylist)
78)
79
80videoPlaylistRouter.delete('/:playlistId',
81 authenticate,
82 asyncMiddleware(videoPlaylistsDeleteValidator),
83 asyncRetryTransactionMiddleware(removeVideoPlaylist)
84)
85
86videoPlaylistRouter.get('/:playlistId/videos',
87 asyncMiddleware(videoPlaylistsGetValidator),
88 paginationValidator,
89 setDefaultPagination,
90 optionalAuthenticate,
91 commonVideosFiltersValidator,
92 asyncMiddleware(getVideoPlaylistVideos)
93)
94
95videoPlaylistRouter.post('/:playlistId/videos',
96 authenticate,
97 asyncMiddleware(videoPlaylistsAddVideoValidator),
98 asyncRetryTransactionMiddleware(addVideoInPlaylist)
99)
100
101videoPlaylistRouter.post('/:playlistId/videos/reorder',
102 authenticate,
103 asyncMiddleware(videoPlaylistsReorderVideosValidator),
104 asyncRetryTransactionMiddleware(reorderVideosPlaylist)
105)
106
107videoPlaylistRouter.put('/:playlistId/videos/:videoId',
108 authenticate,
109 asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator),
110 asyncRetryTransactionMiddleware(updateVideoPlaylistElement)
111)
112
113videoPlaylistRouter.delete('/:playlistId/videos/:videoId',
114 authenticate,
115 asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator),
116 asyncRetryTransactionMiddleware(removeVideoFromPlaylist)
117)
118
119// ---------------------------------------------------------------------------
120
121export {
122 videoPlaylistRouter
123}
124
125// ---------------------------------------------------------------------------
126
127function listVideoPlaylistPrivacies (req: express.Request, res: express.Response) {
128 res.json(VIDEO_PLAYLIST_PRIVACIES)
129}
130
131async function listVideoPlaylists (req: express.Request, res: express.Response) {
132 const serverActor = await getServerActor()
133 const resultList = await VideoPlaylistModel.listForApi({
134 followerActorId: serverActor.id,
135 start: req.query.start,
136 count: req.query.count,
137 sort: req.query.sort,
138 type: req.query.type
139 })
140
141 return res.json(getFormattedObjects(resultList.data, resultList.total))
142}
143
144function getVideoPlaylist (req: express.Request, res: express.Response) {
145 const videoPlaylist = res.locals.videoPlaylist
146
147 if (videoPlaylist.isOutdated()) {
148 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } })
149 .catch(err => logger.error('Cannot create AP refresher job for playlist %s.', videoPlaylist.url, { err }))
150 }
151
152 return res.json(videoPlaylist.toFormattedJSON())
153}
154
155async function addVideoPlaylist (req: express.Request, res: express.Response) {
156 const videoPlaylistInfo: VideoPlaylistCreate = req.body
157 const user = res.locals.oauth.token.User
158
159 const videoPlaylist = new VideoPlaylistModel({
160 name: videoPlaylistInfo.displayName,
161 description: videoPlaylistInfo.description,
162 privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE,
163 ownerAccountId: user.Account.id
164 })
165
166 videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object
167
168 if (videoPlaylistInfo.videoChannelId) {
169 const videoChannel = res.locals.videoChannel
170
171 videoPlaylist.videoChannelId = videoChannel.id
172 videoPlaylist.VideoChannel = videoChannel
173 }
174
175 const thumbnailField = req.files['thumbnailfile']
176 const thumbnailModel = thumbnailField
177 ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist)
178 : undefined
179
180 const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => {
181 const videoPlaylistCreated = await videoPlaylist.save({ transaction: t })
182
183 if (thumbnailModel) await videoPlaylistCreated.setAndSaveThumbnail(thumbnailModel, t)
184
185 // We need more attributes for the federation
186 videoPlaylistCreated.OwnerAccount = await AccountModel.load(user.Account.id, t)
187 await sendCreateVideoPlaylist(videoPlaylistCreated, t)
188
189 return videoPlaylistCreated
190 })
191
192 logger.info('Video playlist with uuid %s created.', videoPlaylist.uuid)
193
194 return res.json({
195 videoPlaylist: {
196 id: videoPlaylistCreated.id,
197 uuid: videoPlaylistCreated.uuid
198 }
199 }).end()
200}
201
202async function updateVideoPlaylist (req: express.Request, res: express.Response) {
203 const videoPlaylistInstance = res.locals.videoPlaylist
204 const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON()
205 const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate
206 const wasPrivatePlaylist = videoPlaylistInstance.privacy === VideoPlaylistPrivacy.PRIVATE
207
208 const thumbnailField = req.files['thumbnailfile']
209 const thumbnailModel = thumbnailField
210 ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylistInstance)
211 : undefined
212
213 try {
214 await sequelizeTypescript.transaction(async t => {
215 const sequelizeOptions = {
216 transaction: t
217 }
218
219 if (videoPlaylistInfoToUpdate.videoChannelId !== undefined) {
220 if (videoPlaylistInfoToUpdate.videoChannelId === null) {
221 videoPlaylistInstance.videoChannelId = null
222 } else {
223 const videoChannel = res.locals.videoChannel
224
225 videoPlaylistInstance.videoChannelId = videoChannel.id
226 videoPlaylistInstance.VideoChannel = videoChannel
227 }
228 }
229
230 if (videoPlaylistInfoToUpdate.displayName !== undefined) videoPlaylistInstance.name = videoPlaylistInfoToUpdate.displayName
231 if (videoPlaylistInfoToUpdate.description !== undefined) videoPlaylistInstance.description = videoPlaylistInfoToUpdate.description
232
233 if (videoPlaylistInfoToUpdate.privacy !== undefined) {
234 videoPlaylistInstance.privacy = parseInt(videoPlaylistInfoToUpdate.privacy.toString(), 10)
235 }
236
237 const playlistUpdated = await videoPlaylistInstance.save(sequelizeOptions)
238
239 if (thumbnailModel) await playlistUpdated.setAndSaveThumbnail(thumbnailModel, t)
240
241 const isNewPlaylist = wasPrivatePlaylist && playlistUpdated.privacy !== VideoPlaylistPrivacy.PRIVATE
242
243 if (isNewPlaylist) {
244 await sendCreateVideoPlaylist(playlistUpdated, t)
245 } else {
246 await sendUpdateVideoPlaylist(playlistUpdated, t)
247 }
248
249 logger.info('Video playlist %s updated.', videoPlaylistInstance.uuid)
250
251 return playlistUpdated
252 })
253 } catch (err) {
254 logger.debug('Cannot update the video playlist.', { err })
255
256 // Force fields we want to update
257 // If the transaction is retried, sequelize will think the object has not changed
258 // So it will skip the SQL request, even if the last one was ROLLBACKed!
259 resetSequelizeInstance(videoPlaylistInstance, videoPlaylistFieldsSave)
260
261 throw err
262 }
263
264 return res.type('json').status(204).end()
265}
266
267async function removeVideoPlaylist (req: express.Request, res: express.Response) {
268 const videoPlaylistInstance = res.locals.videoPlaylist
269
270 await sequelizeTypescript.transaction(async t => {
271 await videoPlaylistInstance.destroy({ transaction: t })
272
273 await sendDeleteVideoPlaylist(videoPlaylistInstance, t)
274
275 logger.info('Video playlist %s deleted.', videoPlaylistInstance.uuid)
276 })
277
278 return res.type('json').status(204).end()
279}
280
281async function addVideoInPlaylist (req: express.Request, res: express.Response) {
282 const body: VideoPlaylistElementCreate = req.body
283 const videoPlaylist = res.locals.videoPlaylist
284 const video = res.locals.video
285
286 const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
287 const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t)
288
289 const playlistElement = await VideoPlaylistElementModel.create({
290 url: getVideoPlaylistElementActivityPubUrl(videoPlaylist, video),
291 position,
292 startTimestamp: body.startTimestamp || null,
293 stopTimestamp: body.stopTimestamp || null,
294 videoPlaylistId: videoPlaylist.id,
295 videoId: video.id
296 }, { transaction: t })
297
298 videoPlaylist.changed('updatedAt', true)
299 await videoPlaylist.save({ transaction: t })
300
301 await sendUpdateVideoPlaylist(videoPlaylist, t)
302
303 return playlistElement
304 })
305
306 // If the user did not set a thumbnail, automatically take the video thumbnail
307 if (videoPlaylist.hasThumbnail() === false) {
308 logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url)
309
310 const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename)
311 const thumbnailModel = await createPlaylistMiniatureFromExisting(inputPath, videoPlaylist, true)
312
313 thumbnailModel.videoPlaylistId = videoPlaylist.id
314
315 await thumbnailModel.save()
316 }
317
318 logger.info('Video added in playlist %s at position %d.', videoPlaylist.uuid, playlistElement.position)
319
320 return res.json({
321 videoPlaylistElement: {
322 id: playlistElement.id
323 }
324 }).end()
325}
326
327async function updateVideoPlaylistElement (req: express.Request, res: express.Response) {
328 const body: VideoPlaylistElementUpdate = req.body
329 const videoPlaylist = res.locals.videoPlaylist
330 const videoPlaylistElement = res.locals.videoPlaylistElement
331
332 const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => {
333 if (body.startTimestamp !== undefined) videoPlaylistElement.startTimestamp = body.startTimestamp
334 if (body.stopTimestamp !== undefined) videoPlaylistElement.stopTimestamp = body.stopTimestamp
335
336 const element = await videoPlaylistElement.save({ transaction: t })
337
338 videoPlaylist.changed('updatedAt', true)
339 await videoPlaylist.save({ transaction: t })
340
341 await sendUpdateVideoPlaylist(videoPlaylist, t)
342
343 return element
344 })
345
346 logger.info('Element of position %d of playlist %s updated.', playlistElement.position, videoPlaylist.uuid)
347
348 return res.type('json').status(204).end()
349}
350
351async function removeVideoFromPlaylist (req: express.Request, res: express.Response) {
352 const videoPlaylistElement = res.locals.videoPlaylistElement
353 const videoPlaylist = res.locals.videoPlaylist
354 const positionToDelete = videoPlaylistElement.position
355
356 await sequelizeTypescript.transaction(async t => {
357 await videoPlaylistElement.destroy({ transaction: t })
358
359 // Decrease position of the next elements
360 await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, positionToDelete, null, -1, t)
361
362 videoPlaylist.changed('updatedAt', true)
363 await videoPlaylist.save({ transaction: t })
364
365 await sendUpdateVideoPlaylist(videoPlaylist, t)
366
367 logger.info('Video playlist element %d of playlist %s deleted.', videoPlaylistElement.position, videoPlaylist.uuid)
368 })
369
370 return res.type('json').status(204).end()
371}
372
373async function reorderVideosPlaylist (req: express.Request, res: express.Response) {
374 const videoPlaylist = res.locals.videoPlaylist
375 const body: VideoPlaylistReorder = req.body
376
377 const start: number = body.startPosition
378 const insertAfter: number = body.insertAfterPosition
379 const reorderLength: number = body.reorderLength || 1
380
381 if (start === insertAfter) {
382 return res.status(204).end()
383 }
384
385 // Example: if we reorder position 2 and insert after position 5 (so at position 6): # 1 2 3 4 5 6 7 8 9
386 // * increase position when position > 5 # 1 2 3 4 5 7 8 9 10
387 // * update position 2 -> position 6 # 1 3 4 5 6 7 8 9 10
388 // * decrease position when position position > 2 # 1 2 3 4 5 6 7 8 9
389 await sequelizeTypescript.transaction(async t => {
390 const newPosition = insertAfter + 1
391
392 // Add space after the position when we want to insert our reordered elements (increase)
393 await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, newPosition, null, reorderLength, t)
394
395 let oldPosition = start
396
397 // We incremented the position of the elements we want to reorder
398 if (start >= newPosition) oldPosition += reorderLength
399
400 const endOldPosition = oldPosition + reorderLength - 1
401 // Insert our reordered elements in their place (update)
402 await VideoPlaylistElementModel.reassignPositionOf(videoPlaylist.id, oldPosition, endOldPosition, newPosition, t)
403
404 // Decrease positions of elements after the old position of our ordered elements (decrease)
405 await VideoPlaylistElementModel.increasePositionOf(videoPlaylist.id, oldPosition, null, -reorderLength, t)
406
407 videoPlaylist.changed('updatedAt', true)
408 await videoPlaylist.save({ transaction: t })
409
410 await sendUpdateVideoPlaylist(videoPlaylist, t)
411 })
412
413 logger.info(
414 'Reordered playlist %s (inserted after %d elements %d - %d).',
415 videoPlaylist.uuid, insertAfter, start, start + reorderLength - 1
416 )
417
418 return res.type('json').status(204).end()
419}
420
421async function getVideoPlaylistVideos (req: express.Request, res: express.Response) {
422 const videoPlaylistInstance = res.locals.videoPlaylist
423 const followerActorId = isUserAbleToSearchRemoteURI(res) ? null : undefined
424
425 const resultList = await VideoModel.listForApi({
426 followerActorId,
427 start: req.query.start,
428 count: req.query.count,
429 sort: 'VideoPlaylistElements.position',
430 includeLocalVideos: true,
431 categoryOneOf: req.query.categoryOneOf,
432 licenceOneOf: req.query.licenceOneOf,
433 languageOneOf: req.query.languageOneOf,
434 tagsOneOf: req.query.tagsOneOf,
435 tagsAllOf: req.query.tagsAllOf,
436 filter: req.query.filter,
437 nsfw: buildNSFWFilter(res, req.query.nsfw),
438 withFiles: false,
439 videoPlaylistId: videoPlaylistInstance.id,
440 user: res.locals.oauth ? res.locals.oauth.token.User : undefined
441 })
442
443 const additionalAttributes = { playlistInfo: true }
444 return res.json(getFormattedObjects(resultList.data, resultList.total, { additionalAttributes }))
445}
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts
index fe0a95cd5..ca70230a2 100644
--- a/server/controllers/api/videos/abuse.ts
+++ b/server/controllers/api/videos/abuse.ts
@@ -3,7 +3,6 @@ import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { sequelizeTypescript } from '../../../initializers' 5import { sequelizeTypescript } from '../../../initializers'
6import { sendVideoAbuse } from '../../../lib/activitypub/send'
7import { 6import {
8 asyncMiddleware, 7 asyncMiddleware,
9 asyncRetryTransactionMiddleware, 8 asyncRetryTransactionMiddleware,
@@ -18,11 +17,10 @@ import {
18 videoAbuseUpdateValidator 17 videoAbuseUpdateValidator
19} from '../../../middlewares' 18} from '../../../middlewares'
20import { AccountModel } from '../../../models/account/account' 19import { AccountModel } from '../../../models/account/account'
21import { VideoModel } from '../../../models/video/video'
22import { VideoAbuseModel } from '../../../models/video/video-abuse' 20import { VideoAbuseModel } from '../../../models/video/video-abuse'
23import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' 21import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger'
24import { UserModel } from '../../../models/account/user'
25import { Notifier } from '../../../lib/notifier' 22import { Notifier } from '../../../lib/notifier'
23import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag'
26 24
27const auditLogger = auditLoggerFactory('abuse') 25const auditLogger = auditLoggerFactory('abuse')
28const abuseVideoRouter = express.Router() 26const abuseVideoRouter = express.Router()
@@ -69,7 +67,7 @@ async function listVideoAbuses (req: express.Request, res: express.Response) {
69} 67}
70 68
71async function updateVideoAbuse (req: express.Request, res: express.Response) { 69async function updateVideoAbuse (req: express.Request, res: express.Response) {
72 const videoAbuse: VideoAbuseModel = res.locals.videoAbuse 70 const videoAbuse = res.locals.videoAbuse
73 71
74 if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment 72 if (req.body.moderationComment !== undefined) videoAbuse.moderationComment = req.body.moderationComment
75 if (req.body.state !== undefined) videoAbuse.state = req.body.state 73 if (req.body.state !== undefined) videoAbuse.state = req.body.state
@@ -84,7 +82,7 @@ async function updateVideoAbuse (req: express.Request, res: express.Response) {
84} 82}
85 83
86async function deleteVideoAbuse (req: express.Request, res: express.Response) { 84async function deleteVideoAbuse (req: express.Request, res: express.Response) {
87 const videoAbuse: VideoAbuseModel = res.locals.videoAbuse 85 const videoAbuse = res.locals.videoAbuse
88 86
89 await sequelizeTypescript.transaction(t => { 87 await sequelizeTypescript.transaction(t => {
90 return videoAbuse.destroy({ transaction: t }) 88 return videoAbuse.destroy({ transaction: t })
@@ -96,11 +94,11 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) {
96} 94}
97 95
98async function reportVideoAbuse (req: express.Request, res: express.Response) { 96async function reportVideoAbuse (req: express.Request, res: express.Response) {
99 const videoInstance = res.locals.video as VideoModel 97 const videoInstance = res.locals.video
100 const body: VideoAbuseCreate = req.body 98 const body: VideoAbuseCreate = req.body
101 99
102 const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => { 100 const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => {
103 const reporterAccount = await AccountModel.load((res.locals.oauth.token.User as UserModel).Account.id, t) 101 const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
104 102
105 const abuseToCreate = { 103 const abuseToCreate = {
106 reporterAccountId: reporterAccount.id, 104 reporterAccountId: reporterAccount.id,
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts
index 43b0516e7..27dcfb761 100644
--- a/server/controllers/api/videos/blacklist.ts
+++ b/server/controllers/api/videos/blacklist.ts
@@ -1,5 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import { VideoBlacklist, UserRight, VideoBlacklistCreate } from '../../../../shared' 2import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { getFormattedObjects } from '../../../helpers/utils' 4import { getFormattedObjects } from '../../../helpers/utils'
5import { 5import {
@@ -12,13 +12,13 @@ import {
12 setDefaultPagination, 12 setDefaultPagination,
13 videosBlacklistAddValidator, 13 videosBlacklistAddValidator,
14 videosBlacklistRemoveValidator, 14 videosBlacklistRemoveValidator,
15 videosBlacklistUpdateValidator 15 videosBlacklistUpdateValidator,
16 videosBlacklistFiltersValidator
16} from '../../../middlewares' 17} from '../../../middlewares'
17import { VideoBlacklistModel } from '../../../models/video/video-blacklist' 18import { VideoBlacklistModel } from '../../../models/video/video-blacklist'
18import { sequelizeTypescript } from '../../../initializers' 19import { sequelizeTypescript } from '../../../initializers'
19import { Notifier } from '../../../lib/notifier' 20import { Notifier } from '../../../lib/notifier'
20import { VideoModel } from '../../../models/video/video' 21import { sendDeleteVideo } from '../../../lib/activitypub/send'
21import { sendCreateVideo, sendDeleteVideo, sendUpdateVideo } from '../../../lib/activitypub/send'
22import { federateVideoIfNeeded } from '../../../lib/activitypub' 22import { federateVideoIfNeeded } from '../../../lib/activitypub'
23 23
24const blacklistRouter = express.Router() 24const blacklistRouter = express.Router()
@@ -37,6 +37,7 @@ blacklistRouter.get('/blacklist',
37 blacklistSortValidator, 37 blacklistSortValidator,
38 setBlacklistSort, 38 setBlacklistSort,
39 setDefaultPagination, 39 setDefaultPagination,
40 videosBlacklistFiltersValidator,
40 asyncMiddleware(listBlacklist) 41 asyncMiddleware(listBlacklist)
41) 42)
42 43
@@ -69,7 +70,8 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
69 const toCreate = { 70 const toCreate = {
70 videoId: videoInstance.id, 71 videoId: videoInstance.id,
71 unfederated: body.unfederate === true, 72 unfederated: body.unfederate === true,
72 reason: body.reason 73 reason: body.reason,
74 type: VideoBlacklistType.MANUAL
73 } 75 }
74 76
75 const blacklist = await VideoBlacklistModel.create(toCreate) 77 const blacklist = await VideoBlacklistModel.create(toCreate)
@@ -87,7 +89,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response)
87} 89}
88 90
89async function updateVideoBlacklistController (req: express.Request, res: express.Response) { 91async function updateVideoBlacklistController (req: express.Request, res: express.Response) {
90 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel 92 const videoBlacklist = res.locals.videoBlacklist
91 93
92 if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason 94 if (req.body.reason !== undefined) videoBlacklist.reason = req.body.reason
93 95
@@ -99,27 +101,39 @@ async function updateVideoBlacklistController (req: express.Request, res: expres
99} 101}
100 102
101async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { 103async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) {
102 const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort) 104 const resultList = await VideoBlacklistModel.listForApi(req.query.start, req.query.count, req.query.sort, req.query.type)
103 105
104 return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total)) 106 return res.json(getFormattedObjects<VideoBlacklist, VideoBlacklistModel>(resultList.data, resultList.total))
105} 107}
106 108
107async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { 109async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) {
108 const videoBlacklist = res.locals.videoBlacklist as VideoBlacklistModel 110 const videoBlacklist = res.locals.videoBlacklist
109 const video: VideoModel = res.locals.video 111 const video = res.locals.video
110 112
111 await sequelizeTypescript.transaction(async t => { 113 const videoBlacklistType = await sequelizeTypescript.transaction(async t => {
112 const unfederated = videoBlacklist.unfederated 114 const unfederated = videoBlacklist.unfederated
115 const videoBlacklistType = videoBlacklist.type
116
113 await videoBlacklist.destroy({ transaction: t }) 117 await videoBlacklist.destroy({ transaction: t })
114 118
115 // Re federate the video 119 // Re federate the video
116 if (unfederated === true) { 120 if (unfederated === true) {
117 await federateVideoIfNeeded(video, true, t) 121 await federateVideoIfNeeded(video, true, t)
118 } 122 }
123
124 return videoBlacklistType
119 }) 125 })
120 126
121 Notifier.Instance.notifyOnVideoUnblacklist(video) 127 Notifier.Instance.notifyOnVideoUnblacklist(video)
122 128
129 if (videoBlacklistType === VideoBlacklistType.AUTO_BEFORE_PUBLISHED) {
130 Notifier.Instance.notifyOnVideoPublishedAfterRemovedFromAutoBlacklist(video)
131
132 // Delete on object so new video notifications will send
133 delete video.VideoBlacklist
134 Notifier.Instance.notifyOnNewVideo(video)
135 }
136
123 logger.info('Video %s removed from blacklist.', res.locals.video.uuid) 137 logger.info('Video %s removed from blacklist.', res.locals.video.uuid)
124 138
125 return res.type('json').status(204).end() 139 return res.type('json').status(204).end()
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts
index 9b3661368..44c255232 100644
--- a/server/controllers/api/videos/captions.ts
+++ b/server/controllers/api/videos/captions.ts
@@ -2,13 +2,14 @@ import * as express from 'express'
2import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' 2import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares'
3import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' 3import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators'
4import { createReqFiles } from '../../../helpers/express-utils' 4import { createReqFiles } from '../../../helpers/express-utils'
5import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers' 5import { 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 { VideoModel } from '../../../models/video/video'
9import { logger } from '../../../helpers/logger' 8import { logger } from '../../../helpers/logger'
10import { federateVideoIfNeeded } from '../../../lib/activitypub' 9import { federateVideoIfNeeded } from '../../../lib/activitypub'
11import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' 10import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils'
11import { CONFIG } from '../../../initializers/config'
12import { sequelizeTypescript } from '../../../initializers/database'
12 13
13const reqVideoCaptionAdd = createReqFiles( 14const reqVideoCaptionAdd = createReqFiles(
14 [ 'captionfile' ], 15 [ 'captionfile' ],
@@ -52,7 +53,7 @@ async function listVideoCaptions (req: express.Request, res: express.Response) {
52 53
53async function addVideoCaption (req: express.Request, res: express.Response) { 54async function addVideoCaption (req: express.Request, res: express.Response) {
54 const videoCaptionPhysicalFile = req.files['captionfile'][0] 55 const videoCaptionPhysicalFile = req.files['captionfile'][0]
55 const video = res.locals.video as VideoModel 56 const video = res.locals.video
56 57
57 const videoCaption = new VideoCaptionModel({ 58 const videoCaption = new VideoCaptionModel({
58 videoId: video.id, 59 videoId: video.id,
@@ -74,8 +75,8 @@ async function addVideoCaption (req: express.Request, res: express.Response) {
74} 75}
75 76
76async function deleteVideoCaption (req: express.Request, res: express.Response) { 77async function deleteVideoCaption (req: express.Request, res: express.Response) {
77 const video = res.locals.video as VideoModel 78 const video = res.locals.video
78 const videoCaption = res.locals.videoCaption as VideoCaptionModel 79 const videoCaption = res.locals.videoCaption
79 80
80 await sequelizeTypescript.transaction(async t => { 81 await sequelizeTypescript.transaction(async t => {
81 await videoCaption.destroy({ transaction: t }) 82 await videoCaption.destroy({ transaction: t })
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts
index 70c1148ba..176ee8bd4 100644
--- a/server/controllers/api/videos/comment.ts
+++ b/server/controllers/api/videos/comment.ts
@@ -8,7 +8,8 @@ import { buildFormattedCommentTree, createVideoComment } from '../../../lib/vide
8import { 8import {
9 asyncMiddleware, 9 asyncMiddleware,
10 asyncRetryTransactionMiddleware, 10 asyncRetryTransactionMiddleware,
11 authenticate, optionalAuthenticate, 11 authenticate,
12 optionalAuthenticate,
12 paginationValidator, 13 paginationValidator,
13 setDefaultPagination, 14 setDefaultPagination,
14 setDefaultSort 15 setDefaultSort
@@ -21,11 +22,9 @@ import {
21 removeVideoCommentValidator, 22 removeVideoCommentValidator,
22 videoCommentThreadsSortValidator 23 videoCommentThreadsSortValidator
23} from '../../../middlewares/validators' 24} from '../../../middlewares/validators'
24import { VideoModel } from '../../../models/video/video'
25import { VideoCommentModel } from '../../../models/video/video-comment' 25import { VideoCommentModel } from '../../../models/video/video-comment'
26import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' 26import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger'
27import { AccountModel } from '../../../models/account/account' 27import { AccountModel } from '../../../models/account/account'
28import { UserModel } from '../../../models/account/user'
29import { Notifier } from '../../../lib/notifier' 28import { Notifier } from '../../../lib/notifier'
30 29
31const auditLogger = auditLoggerFactory('comments') 30const auditLogger = auditLoggerFactory('comments')
@@ -70,9 +69,9 @@ export {
70 69
71// --------------------------------------------------------------------------- 70// ---------------------------------------------------------------------------
72 71
73async function listVideoThreads (req: express.Request, res: express.Response, next: express.NextFunction) { 72async function listVideoThreads (req: express.Request, res: express.Response) {
74 const video = res.locals.video as VideoModel 73 const video = res.locals.video
75 const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined 74 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
76 75
77 let resultList: ResultList<VideoCommentModel> 76 let resultList: ResultList<VideoCommentModel>
78 77
@@ -88,9 +87,9 @@ async function listVideoThreads (req: express.Request, res: express.Response, ne
88 return res.json(getFormattedObjects(resultList.data, resultList.total)) 87 return res.json(getFormattedObjects(resultList.data, resultList.total))
89} 88}
90 89
91async function listVideoThreadComments (req: express.Request, res: express.Response, next: express.NextFunction) { 90async function listVideoThreadComments (req: express.Request, res: express.Response) {
92 const video = res.locals.video as VideoModel 91 const video = res.locals.video
93 const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined 92 const user = res.locals.oauth ? res.locals.oauth.token.User : undefined
94 93
95 let resultList: ResultList<VideoCommentModel> 94 let resultList: ResultList<VideoCommentModel>
96 95
@@ -110,7 +109,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons
110 const videoCommentInfo: VideoCommentCreate = req.body 109 const videoCommentInfo: VideoCommentCreate = req.body
111 110
112 const comment = await sequelizeTypescript.transaction(async t => { 111 const comment = await sequelizeTypescript.transaction(async t => {
113 const account = await AccountModel.load((res.locals.oauth.token.User as UserModel).Account.id, t) 112 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
114 113
115 return createVideoComment({ 114 return createVideoComment({
116 text: videoCommentInfo.text, 115 text: videoCommentInfo.text,
@@ -132,7 +131,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
132 const videoCommentInfo: VideoCommentCreate = req.body 131 const videoCommentInfo: VideoCommentCreate = req.body
133 132
134 const comment = await sequelizeTypescript.transaction(async t => { 133 const comment = await sequelizeTypescript.transaction(async t => {
135 const account = await AccountModel.load((res.locals.oauth.token.User as UserModel).Account.id, t) 134 const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t)
136 135
137 return createVideoComment({ 136 return createVideoComment({
138 text: videoCommentInfo.text, 137 text: videoCommentInfo.text,
@@ -149,7 +148,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response
149} 148}
150 149
151async function removeVideoComment (req: express.Request, res: express.Response) { 150async function removeVideoComment (req: express.Request, res: express.Response) {
152 const videoCommentInstance: VideoCommentModel = res.locals.videoComment 151 const videoCommentInstance = res.locals.videoComment
153 152
154 await sequelizeTypescript.transaction(async t => { 153 await sequelizeTypescript.transaction(async t => {
155 await videoCommentInstance.destroy({ transaction: t }) 154 await videoCommentInstance.destroy({ transaction: t })
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts
index 98366cd82..bfb690906 100644
--- a/server/controllers/api/videos/import.ts
+++ b/server/controllers/api/videos/import.ts
@@ -3,7 +3,7 @@ import * as magnetUtil from 'magnet-uri'
3import 'multer' 3import 'multer'
4import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' 4import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger'
5import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' 5import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares'
6import { CONFIG, MIMETYPES, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' 6import { MIMETYPES } from '../../../initializers/constants'
7import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' 7import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl'
8import { createReqFiles } from '../../../helpers/express-utils' 8import { createReqFiles } from '../../../helpers/express-utils'
9import { logger } from '../../../helpers/logger' 9import { logger } from '../../../helpers/logger'
@@ -13,15 +13,19 @@ import { getVideoActivityPubUrl } from '../../../lib/activitypub'
13import { TagModel } from '../../../models/video/tag' 13import { TagModel } from '../../../models/video/tag'
14import { VideoImportModel } from '../../../models/video/video-import' 14import { VideoImportModel } from '../../../models/video/video-import'
15import { JobQueue } from '../../../lib/job-queue/job-queue' 15import { JobQueue } from '../../../lib/job-queue/job-queue'
16import { processImage } from '../../../helpers/image-utils'
17import { join } from 'path' 16import { join } from 'path'
18import { isArray } from '../../../helpers/custom-validators/misc' 17import { isArray } from '../../../helpers/custom-validators/misc'
19import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model'
20import { VideoChannelModel } from '../../../models/video/video-channel' 18import { VideoChannelModel } from '../../../models/video/video-channel'
21import * as Bluebird from 'bluebird' 19import * as Bluebird from 'bluebird'
22import * as parseTorrent from 'parse-torrent' 20import * as parseTorrent from 'parse-torrent'
23import { getSecureTorrentName } from '../../../helpers/utils' 21import { getSecureTorrentName } from '../../../helpers/utils'
24import { readFile, move } from 'fs-extra' 22import { move, readFile } from 'fs-extra'
23import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
24import { CONFIG } from '../../../initializers/config'
25import { sequelizeTypescript } from '../../../initializers/database'
26import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail'
27import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
28import { ThumbnailModel } from '../../../models/video/thumbnail'
25 29
26const auditLogger = auditLoggerFactory('video-imports') 30const auditLogger = auditLoggerFactory('video-imports')
27const videoImportsRouter = express.Router() 31const videoImportsRouter = express.Router()
@@ -87,8 +91,8 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
87 91
88 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName }) 92 const video = buildVideo(res.locals.videoChannel.id, body, { name: videoName })
89 93
90 await processThumbnail(req, video) 94 const thumbnailModel = await processThumbnail(req, video)
91 await processPreview(req, video) 95 const previewModel = await processPreview(req, video)
92 96
93 const tags = body.tags || undefined 97 const tags = body.tags || undefined
94 const videoImportAttributes = { 98 const videoImportAttributes = {
@@ -97,7 +101,14 @@ async function addTorrentImport (req: express.Request, res: express.Response, to
97 state: VideoImportState.PENDING, 101 state: VideoImportState.PENDING,
98 userId: user.id 102 userId: user.id
99 } 103 }
100 const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes) 104 const videoImport = await insertIntoDB({
105 video,
106 thumbnailModel,
107 previewModel,
108 videoChannel: res.locals.videoChannel,
109 tags,
110 videoImportAttributes
111 })
101 112
102 // Create job to import the video 113 // Create job to import the video
103 const payload = { 114 const payload = {
@@ -130,8 +141,8 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
130 141
131 const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo) 142 const video = buildVideo(res.locals.videoChannel.id, body, youtubeDLInfo)
132 143
133 const downloadThumbnail = !await processThumbnail(req, video) 144 const thumbnailModel = await processThumbnail(req, video)
134 const downloadPreview = !await processPreview(req, video) 145 const previewModel = await processPreview(req, video)
135 146
136 const tags = body.tags || youtubeDLInfo.tags 147 const tags = body.tags || youtubeDLInfo.tags
137 const videoImportAttributes = { 148 const videoImportAttributes = {
@@ -139,15 +150,22 @@ async function addYoutubeDLImport (req: express.Request, res: express.Response)
139 state: VideoImportState.PENDING, 150 state: VideoImportState.PENDING,
140 userId: user.id 151 userId: user.id
141 } 152 }
142 const videoImport: VideoImportModel = await insertIntoDB(video, res.locals.videoChannel, tags, videoImportAttributes) 153 const videoImport = await insertIntoDB({
154 video: video,
155 thumbnailModel,
156 previewModel,
157 videoChannel: res.locals.videoChannel,
158 tags,
159 videoImportAttributes
160 })
143 161
144 // Create job to import the video 162 // Create job to import the video
145 const payload = { 163 const payload = {
146 type: 'youtube-dl' as 'youtube-dl', 164 type: 'youtube-dl' as 'youtube-dl',
147 videoImportId: videoImport.id, 165 videoImportId: videoImport.id,
148 thumbnailUrl: youtubeDLInfo.thumbnailUrl, 166 thumbnailUrl: youtubeDLInfo.thumbnailUrl,
149 downloadThumbnail, 167 downloadThumbnail: !thumbnailModel,
150 downloadPreview 168 downloadPreview: !previewModel
151 } 169 }
152 await JobQueue.Instance.createJob({ type: 'video-import', payload }) 170 await JobQueue.Instance.createJob({ type: 'video-import', payload })
153 171
@@ -164,6 +182,7 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
164 licence: body.licence || importData.licence, 182 licence: body.licence || importData.licence,
165 language: body.language || undefined, 183 language: body.language || undefined,
166 commentsEnabled: body.commentsEnabled || true, 184 commentsEnabled: body.commentsEnabled || true,
185 downloadEnabled: body.downloadEnabled || true,
167 waitTranscoding: body.waitTranscoding || false, 186 waitTranscoding: body.waitTranscoding || false,
168 state: VideoState.TO_IMPORT, 187 state: VideoState.TO_IMPORT,
169 nsfw: body.nsfw || importData.nsfw || false, 188 nsfw: body.nsfw || importData.nsfw || false,
@@ -171,7 +190,8 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You
171 support: body.support || null, 190 support: body.support || null,
172 privacy: body.privacy || VideoPrivacy.PRIVATE, 191 privacy: body.privacy || VideoPrivacy.PRIVATE,
173 duration: 0, // duration will be set by the import job 192 duration: 0, // duration will be set by the import job
174 channelId: channelId 193 channelId: channelId,
194 originallyPublishedAt: importData.originallyPublishedAt
175 } 195 }
176 const video = new VideoModel(videoData) 196 const video = new VideoModel(videoData)
177 video.url = getVideoActivityPubUrl(video) 197 video.url = getVideoActivityPubUrl(video)
@@ -183,32 +203,34 @@ async function processThumbnail (req: express.Request, video: VideoModel) {
183 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined 203 const thumbnailField = req.files ? req.files['thumbnailfile'] : undefined
184 if (thumbnailField) { 204 if (thumbnailField) {
185 const thumbnailPhysicalFile = thumbnailField[ 0 ] 205 const thumbnailPhysicalFile = thumbnailField[ 0 ]
186 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE)
187 206
188 return true 207 return createVideoMiniatureFromExisting(thumbnailPhysicalFile.path, video, ThumbnailType.MINIATURE)
189 } 208 }
190 209
191 return false 210 return undefined
192} 211}
193 212
194async function processPreview (req: express.Request, video: VideoModel) { 213async function processPreview (req: express.Request, video: VideoModel) {
195 const previewField = req.files ? req.files['previewfile'] : undefined 214 const previewField = req.files ? req.files['previewfile'] : undefined
196 if (previewField) { 215 if (previewField) {
197 const previewPhysicalFile = previewField[0] 216 const previewPhysicalFile = previewField[0]
198 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE)
199 217
200 return true 218 return createVideoMiniatureFromExisting(previewPhysicalFile.path, video, ThumbnailType.PREVIEW)
201 } 219 }
202 220
203 return false 221 return undefined
204} 222}
205 223
206function insertIntoDB ( 224function insertIntoDB (parameters: {
207 video: VideoModel, 225 video: VideoModel,
226 thumbnailModel: ThumbnailModel,
227 previewModel: ThumbnailModel,
208 videoChannel: VideoChannelModel, 228 videoChannel: VideoChannelModel,
209 tags: string[], 229 tags: string[],
210 videoImportAttributes: FilteredModelAttributes<VideoImportModel> 230 videoImportAttributes: Partial<VideoImportModel>
211): Bluebird<VideoImportModel> { 231}): Bluebird<VideoImportModel> {
232 let { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes } = parameters
233
212 return sequelizeTypescript.transaction(async t => { 234 return sequelizeTypescript.transaction(async t => {
213 const sequelizeOptions = { transaction: t } 235 const sequelizeOptions = { transaction: t }
214 236
@@ -216,6 +238,11 @@ function insertIntoDB (
216 const videoCreated = await video.save(sequelizeOptions) 238 const videoCreated = await video.save(sequelizeOptions)
217 videoCreated.VideoChannel = videoChannel 239 videoCreated.VideoChannel = videoChannel
218 240
241 if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
242 if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t)
243
244 await autoBlacklistVideoIfNeeded(video, videoChannel.Account.User, t)
245
219 // Set tags to the video 246 // Set tags to the video
220 if (tags) { 247 if (tags) {
221 const tagInstances = await TagModel.findOrCreateTags(tags, t) 248 const tagInstances = await TagModel.findOrCreateTags(tags, t)
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts
index 2b2dfa7ca..1a18a8ae8 100644
--- a/server/controllers/api/videos/index.ts
+++ b/server/controllers/api/videos/index.ts
@@ -2,28 +2,17 @@ import * as express from 'express'
2import { extname, join } from 'path' 2import { extname, join } from 'path'
3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' 3import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared'
4import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' 4import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils'
5import { processImage } from '../../../helpers/image-utils'
6import { logger } from '../../../helpers/logger' 5import { logger } from '../../../helpers/logger'
7import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' 6import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger'
8import { getFormattedObjects, getServerActor } from '../../../helpers/utils' 7import { getFormattedObjects, getServerActor } from '../../../helpers/utils'
9import { 8import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist'
10 CONFIG, 9import { MIMETYPES, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PRIVACIES } from '../../../initializers/constants'
11 MIMETYPES,
12 PREVIEWS_SIZE,
13 sequelizeTypescript,
14 THUMBNAILS_SIZE,
15 VIDEO_CATEGORIES,
16 VIDEO_LANGUAGES,
17 VIDEO_LICENCES,
18 VIDEO_PRIVACIES
19} from '../../../initializers'
20import { 10import {
21 changeVideoChannelShare, 11 changeVideoChannelShare,
22 federateVideoIfNeeded, 12 federateVideoIfNeeded,
23 fetchRemoteVideoDescription, 13 fetchRemoteVideoDescription,
24 getVideoActivityPubUrl 14 getVideoActivityPubUrl
25} from '../../../lib/activitypub' 15} from '../../../lib/activitypub'
26import { sendCreateView } from '../../../lib/activitypub/send'
27import { JobQueue } from '../../../lib/job-queue' 16import { JobQueue } from '../../../lib/job-queue'
28import { Redis } from '../../../lib/redis' 17import { Redis } from '../../../lib/redis'
29import { 18import {
@@ -37,6 +26,7 @@ import {
37 setDefaultPagination, 26 setDefaultPagination,
38 setDefaultSort, 27 setDefaultSort,
39 videosAddValidator, 28 videosAddValidator,
29 videosCustomGetValidator,
40 videosGetValidator, 30 videosGetValidator,
41 videosRemoveValidator, 31 videosRemoveValidator,
42 videosSortValidator, 32 videosSortValidator,
@@ -59,6 +49,11 @@ import { resetSequelizeInstance } from '../../../helpers/database-utils'
59import { move } from 'fs-extra' 49import { move } from 'fs-extra'
60import { watchingRouter } from './watching' 50import { watchingRouter } from './watching'
61import { Notifier } from '../../../lib/notifier' 51import { Notifier } from '../../../lib/notifier'
52import { sendView } from '../../../lib/activitypub/send/send-view'
53import { CONFIG } from '../../../initializers/config'
54import { sequelizeTypescript } from '../../../initializers/database'
55import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail'
56import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type'
62 57
63const auditLogger = auditLoggerFactory('videos') 58const auditLogger = auditLoggerFactory('videos')
64const videosRouter = express.Router() 59const videosRouter = express.Router()
@@ -123,9 +118,9 @@ videosRouter.get('/:id/description',
123) 118)
124videosRouter.get('/:id', 119videosRouter.get('/:id',
125 optionalAuthenticate, 120 optionalAuthenticate,
126 asyncMiddleware(videosGetValidator), 121 asyncMiddleware(videosCustomGetValidator('only-video-with-rights')),
127 asyncMiddleware(checkVideoFollowConstraints), 122 asyncMiddleware(checkVideoFollowConstraints),
128 getVideo 123 asyncMiddleware(getVideo)
129) 124)
130videosRouter.post('/:id/views', 125videosRouter.post('/:id/views',
131 asyncMiddleware(videosGetValidator), 126 asyncMiddleware(videosGetValidator),
@@ -181,15 +176,18 @@ async function addVideo (req: express.Request, res: express.Response) {
181 licence: videoInfo.licence, 176 licence: videoInfo.licence,
182 language: videoInfo.language, 177 language: videoInfo.language,
183 commentsEnabled: videoInfo.commentsEnabled || false, 178 commentsEnabled: videoInfo.commentsEnabled || false,
179 downloadEnabled: videoInfo.downloadEnabled || true,
184 waitTranscoding: videoInfo.waitTranscoding || false, 180 waitTranscoding: videoInfo.waitTranscoding || false,
185 state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, 181 state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED,
186 nsfw: videoInfo.nsfw || false, 182 nsfw: videoInfo.nsfw || false,
187 description: videoInfo.description, 183 description: videoInfo.description,
188 support: videoInfo.support, 184 support: videoInfo.support,
189 privacy: videoInfo.privacy, 185 privacy: videoInfo.privacy || VideoPrivacy.PRIVATE,
190 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware 186 duration: videoPhysicalFile['duration'], // duration was added by a previous middleware
191 channelId: res.locals.videoChannel.id 187 channelId: res.locals.videoChannel.id,
188 originallyPublishedAt: videoInfo.originallyPublishedAt
192 } 189 }
190
193 const video = new VideoModel(videoData) 191 const video = new VideoModel(videoData)
194 video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object 192 video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object
195 193
@@ -215,29 +213,27 @@ async function addVideo (req: express.Request, res: express.Response) {
215 213
216 // Process thumbnail or create it from the video 214 // Process thumbnail or create it from the video
217 const thumbnailField = req.files['thumbnailfile'] 215 const thumbnailField = req.files['thumbnailfile']
218 if (thumbnailField) { 216 const thumbnailModel = thumbnailField
219 const thumbnailPhysicalFile = thumbnailField[0] 217 ? await createVideoMiniatureFromExisting(thumbnailField[0].path, video, ThumbnailType.MINIATURE)
220 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getThumbnailName()), THUMBNAILS_SIZE) 218 : await generateVideoMiniature(video, videoFile, ThumbnailType.MINIATURE)
221 } else {
222 await video.createThumbnail(videoFile)
223 }
224 219
225 // Process preview or create it from the video 220 // Process preview or create it from the video
226 const previewField = req.files['previewfile'] 221 const previewField = req.files['previewfile']
227 if (previewField) { 222 const previewModel = previewField
228 const previewPhysicalFile = previewField[0] 223 ? await createVideoMiniatureFromExisting(previewField[0].path, video, ThumbnailType.PREVIEW)
229 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()), PREVIEWS_SIZE) 224 : await generateVideoMiniature(video, videoFile, ThumbnailType.PREVIEW)
230 } else {
231 await video.createPreview(videoFile)
232 }
233 225
234 // Create the torrent file 226 // Create the torrent file
235 await video.createTorrentAndSetInfoHash(videoFile) 227 await video.createTorrentAndSetInfoHash(videoFile)
236 228
237 const videoCreated = await sequelizeTypescript.transaction(async t => { 229 const { videoCreated, videoWasAutoBlacklisted } = await sequelizeTypescript.transaction(async t => {
238 const sequelizeOptions = { transaction: t } 230 const sequelizeOptions = { transaction: t }
239 231
240 const videoCreated = await video.save(sequelizeOptions) 232 const videoCreated = await video.save(sequelizeOptions)
233
234 await videoCreated.addAndSaveThumbnail(thumbnailModel, t)
235 await videoCreated.addAndSaveThumbnail(previewModel, t)
236
241 // Do not forget to add video channel information to the created video 237 // Do not forget to add video channel information to the created video
242 videoCreated.VideoChannel = res.locals.videoChannel 238 videoCreated.VideoChannel = res.locals.videoChannel
243 239
@@ -263,15 +259,23 @@ async function addVideo (req: express.Request, res: express.Response) {
263 }, { transaction: t }) 259 }, { transaction: t })
264 } 260 }
265 261
266 await federateVideoIfNeeded(video, true, t) 262 const videoWasAutoBlacklisted = await autoBlacklistVideoIfNeeded(video, res.locals.oauth.token.User, t)
263
264 if (!videoWasAutoBlacklisted) {
265 await federateVideoIfNeeded(video, true, t)
266 }
267 267
268 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON())) 268 auditLogger.create(getAuditIdFromRes(res), new VideoAuditView(videoCreated.toFormattedDetailsJSON()))
269 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid) 269 logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoCreated.uuid)
270 270
271 return videoCreated 271 return { videoCreated, videoWasAutoBlacklisted }
272 }) 272 })
273 273
274 Notifier.Instance.notifyOnNewVideo(videoCreated) 274 if (videoWasAutoBlacklisted) {
275 Notifier.Instance.notifyOnVideoAutoBlacklist(videoCreated)
276 } else {
277 Notifier.Instance.notifyOnNewVideo(videoCreated)
278 }
275 279
276 if (video.state === VideoState.TO_TRANSCODE) { 280 if (video.state === VideoState.TO_TRANSCODE) {
277 // Put uuid because we don't have id auto incremented for now 281 // Put uuid because we don't have id auto incremented for now
@@ -280,7 +284,7 @@ async function addVideo (req: express.Request, res: express.Response) {
280 isNewVideo: true 284 isNewVideo: true
281 } 285 }
282 286
283 await JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput }) 287 await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput })
284 } 288 }
285 289
286 return res.json({ 290 return res.json({
@@ -292,7 +296,7 @@ async function addVideo (req: express.Request, res: express.Response) {
292} 296}
293 297
294async function updateVideo (req: express.Request, res: express.Response) { 298async function updateVideo (req: express.Request, res: express.Response) {
295 const videoInstance: VideoModel = res.locals.video 299 const videoInstance = res.locals.video
296 const videoFieldsSave = videoInstance.toJSON() 300 const videoFieldsSave = videoInstance.toJSON()
297 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) 301 const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON())
298 const videoInfoToUpdate: VideoUpdate = req.body 302 const videoInfoToUpdate: VideoUpdate = req.body
@@ -300,16 +304,13 @@ async function updateVideo (req: express.Request, res: express.Response) {
300 const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED 304 const wasUnlistedVideo = videoInstance.privacy === VideoPrivacy.UNLISTED
301 305
302 // Process thumbnail or create it from the video 306 // Process thumbnail or create it from the video
303 if (req.files && req.files['thumbnailfile']) { 307 const thumbnailModel = req.files && req.files['thumbnailfile']
304 const thumbnailPhysicalFile = req.files['thumbnailfile'][0] 308 ? await createVideoMiniatureFromExisting(req.files['thumbnailfile'][0].path, videoInstance, ThumbnailType.MINIATURE)
305 await processImage(thumbnailPhysicalFile, join(CONFIG.STORAGE.THUMBNAILS_DIR, videoInstance.getThumbnailName()), THUMBNAILS_SIZE) 309 : undefined
306 }
307 310
308 // Process preview or create it from the video 311 const previewModel = req.files && req.files['previewfile']
309 if (req.files && req.files['previewfile']) { 312 ? await createVideoMiniatureFromExisting(req.files['previewfile'][0].path, videoInstance, ThumbnailType.PREVIEW)
310 const previewPhysicalFile = req.files['previewfile'][0] 313 : undefined
311 await processImage(previewPhysicalFile, join(CONFIG.STORAGE.PREVIEWS_DIR, videoInstance.getPreviewName()), PREVIEWS_SIZE)
312 }
313 314
314 try { 315 try {
315 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => { 316 const videoInstanceUpdated = await sequelizeTypescript.transaction(async t => {
@@ -325,17 +326,26 @@ async function updateVideo (req: express.Request, res: express.Response) {
325 if (videoInfoToUpdate.support !== undefined) videoInstance.set('support', videoInfoToUpdate.support) 326 if (videoInfoToUpdate.support !== undefined) videoInstance.set('support', videoInfoToUpdate.support)
326 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description) 327 if (videoInfoToUpdate.description !== undefined) videoInstance.set('description', videoInfoToUpdate.description)
327 if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled) 328 if (videoInfoToUpdate.commentsEnabled !== undefined) videoInstance.set('commentsEnabled', videoInfoToUpdate.commentsEnabled)
329 if (videoInfoToUpdate.downloadEnabled !== undefined) videoInstance.set('downloadEnabled', videoInfoToUpdate.downloadEnabled)
330
331 if (videoInfoToUpdate.originallyPublishedAt !== undefined && videoInfoToUpdate.originallyPublishedAt !== null) {
332 videoInstance.originallyPublishedAt = new Date(videoInfoToUpdate.originallyPublishedAt)
333 }
334
328 if (videoInfoToUpdate.privacy !== undefined) { 335 if (videoInfoToUpdate.privacy !== undefined) {
329 const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10) 336 const newPrivacy = parseInt(videoInfoToUpdate.privacy.toString(), 10)
330 videoInstance.set('privacy', newPrivacy) 337 videoInstance.privacy = newPrivacy
331 338
332 if (wasPrivateVideo === true && newPrivacy !== VideoPrivacy.PRIVATE) { 339 if (wasPrivateVideo === true && newPrivacy !== VideoPrivacy.PRIVATE) {
333 videoInstance.set('publishedAt', new Date()) 340 videoInstance.publishedAt = new Date()
334 } 341 }
335 } 342 }
336 343
337 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) 344 const videoInstanceUpdated = await videoInstance.save(sequelizeOptions)
338 345
346 if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t)
347 if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t)
348
339 // Video tags update? 349 // Video tags update?
340 if (videoInfoToUpdate.tags !== undefined) { 350 if (videoInfoToUpdate.tags !== undefined) {
341 const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t) 351 const tagInstances = await TagModel.findOrCreateTags(videoInfoToUpdate.tags, t)
@@ -395,22 +405,24 @@ async function updateVideo (req: express.Request, res: express.Response) {
395 return res.type('json').status(204).end() 405 return res.type('json').status(204).end()
396} 406}
397 407
398function getVideo (req: express.Request, res: express.Response) { 408async function getVideo (req: express.Request, res: express.Response) {
399 const videoInstance = res.locals.video 409 // We need more attributes
410 const userId: number = res.locals.oauth ? res.locals.oauth.token.User.id : null
411 const video = await VideoModel.loadForGetAPI(res.locals.video.id, undefined, userId)
400 412
401 if (videoInstance.isOutdated()) { 413 if (video.isOutdated()) {
402 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: videoInstance.url } }) 414 JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video', url: video.url } })
403 .catch(err => logger.error('Cannot create AP refresher job for video %s.', videoInstance.url, { err })) 415 .catch(err => logger.error('Cannot create AP refresher job for video %s.', video.url, { err }))
404 } 416 }
405 417
406 return res.json(videoInstance.toFormattedDetailsJSON()) 418 return res.json(video.toFormattedDetailsJSON())
407} 419}
408 420
409async function viewVideo (req: express.Request, res: express.Response) { 421async function viewVideo (req: express.Request, res: express.Response) {
410 const videoInstance = res.locals.video 422 const videoInstance = res.locals.video
411 423
412 const ip = req.ip 424 const ip = req.ip
413 const exists = await Redis.Instance.isVideoIPViewExists(ip, videoInstance.uuid) 425 const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid)
414 if (exists) { 426 if (exists) {
415 logger.debug('View for ip %s and video %s already exists.', ip, videoInstance.uuid) 427 logger.debug('View for ip %s and video %s already exists.', ip, videoInstance.uuid)
416 return res.status(204).end() 428 return res.status(204).end()
@@ -422,7 +434,7 @@ async function viewVideo (req: express.Request, res: express.Response) {
422 ]) 434 ])
423 435
424 const serverActor = await getServerActor() 436 const serverActor = await getServerActor()
425 await sendCreateView(serverActor, videoInstance, undefined) 437 await sendView(serverActor, videoInstance, undefined)
426 438
427 return res.status(204).end() 439 return res.status(204).end()
428} 440}
@@ -461,7 +473,7 @@ async function listVideos (req: express.Request, res: express.Response) {
461} 473}
462 474
463async function removeVideo (req: express.Request, res: express.Response) { 475async function removeVideo (req: express.Request, res: express.Response) {
464 const videoInstance: VideoModel = res.locals.video 476 const videoInstance = res.locals.video
465 477
466 await sequelizeTypescript.transaction(async t => { 478 await sequelizeTypescript.transaction(async t => {
467 await videoInstance.destroy({ transaction: t }) 479 await videoInstance.destroy({ transaction: t })
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts
index 5ea7d7c6a..5272c1385 100644
--- a/server/controllers/api/videos/ownership.ts
+++ b/server/controllers/api/videos/ownership.ts
@@ -11,15 +11,13 @@ import {
11 videosChangeOwnershipValidator, 11 videosChangeOwnershipValidator,
12 videosTerminateChangeOwnershipValidator 12 videosTerminateChangeOwnershipValidator
13} from '../../../middlewares' 13} from '../../../middlewares'
14import { AccountModel } from '../../../models/account/account'
15import { VideoModel } from '../../../models/video/video'
16import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' 14import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership'
17import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos' 15import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos'
18import { VideoChannelModel } from '../../../models/video/video-channel' 16import { VideoChannelModel } from '../../../models/video/video-channel'
19import { getFormattedObjects } from '../../../helpers/utils' 17import { getFormattedObjects } from '../../../helpers/utils'
20import { changeVideoChannelShare } from '../../../lib/activitypub' 18import { changeVideoChannelShare } from '../../../lib/activitypub'
21import { sendUpdateVideo } from '../../../lib/activitypub/send' 19import { sendUpdateVideo } from '../../../lib/activitypub/send'
22import { UserModel } from '../../../models/account/user' 20import { VideoModel } from '../../../models/video/video'
23 21
24const ownershipVideoRouter = express.Router() 22const ownershipVideoRouter = express.Router()
25 23
@@ -58,9 +56,9 @@ export {
58// --------------------------------------------------------------------------- 56// ---------------------------------------------------------------------------
59 57
60async function giveVideoOwnership (req: express.Request, res: express.Response) { 58async function giveVideoOwnership (req: express.Request, res: express.Response) {
61 const videoInstance = res.locals.video as VideoModel 59 const videoInstance = res.locals.video
62 const initiatorAccountId = (res.locals.oauth.token.User as UserModel).Account.id 60 const initiatorAccountId = res.locals.oauth.token.User.Account.id
63 const nextOwner = res.locals.nextOwner as AccountModel 61 const nextOwner = res.locals.nextOwner
64 62
65 await sequelizeTypescript.transaction(t => { 63 await sequelizeTypescript.transaction(t => {
66 return VideoChangeOwnershipModel.findOrCreate({ 64 return VideoChangeOwnershipModel.findOrCreate({
@@ -85,7 +83,7 @@ async function giveVideoOwnership (req: express.Request, res: express.Response)
85} 83}
86 84
87async function listVideoOwnership (req: express.Request, res: express.Response) { 85async function listVideoOwnership (req: express.Request, res: express.Response) {
88 const currentAccountId = (res.locals.oauth.token.User as UserModel).Account.id 86 const currentAccountId = res.locals.oauth.token.User.Account.id
89 87
90 const resultList = await VideoChangeOwnershipModel.listForApi( 88 const resultList = await VideoChangeOwnershipModel.listForApi(
91 currentAccountId, 89 currentAccountId,
@@ -99,13 +97,16 @@ async function listVideoOwnership (req: express.Request, res: express.Response)
99 97
100async function acceptOwnership (req: express.Request, res: express.Response) { 98async function acceptOwnership (req: express.Request, res: express.Response) {
101 return sequelizeTypescript.transaction(async t => { 99 return sequelizeTypescript.transaction(async t => {
102 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel 100 const videoChangeOwnership = res.locals.videoChangeOwnership
103 const targetVideo = videoChangeOwnership.Video 101 const channel = res.locals.videoChannel
104 const channel = res.locals.videoChannel as VideoChannelModel 102
103 // We need more attributes for federation
104 const targetVideo = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoChangeOwnership.Video.id)
105 105
106 const oldVideoChannel = await VideoChannelModel.loadByIdAndPopulateAccount(targetVideo.channelId) 106 const oldVideoChannel = await VideoChannelModel.loadByIdAndPopulateAccount(targetVideo.channelId)
107 107
108 targetVideo.set('channelId', channel.id) 108 targetVideo.channelId = channel.id
109
109 const targetVideoUpdated = await targetVideo.save({ transaction: t }) 110 const targetVideoUpdated = await targetVideo.save({ transaction: t })
110 targetVideoUpdated.VideoChannel = channel 111 targetVideoUpdated.VideoChannel = channel
111 112
@@ -114,7 +115,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
114 await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor) 115 await sendUpdateVideo(targetVideoUpdated, t, oldVideoChannel.Account.Actor)
115 } 116 }
116 117
117 videoChangeOwnership.set('status', VideoChangeOwnershipStatus.ACCEPTED) 118 videoChangeOwnership.status = VideoChangeOwnershipStatus.ACCEPTED
118 await videoChangeOwnership.save({ transaction: t }) 119 await videoChangeOwnership.save({ transaction: t })
119 120
120 return res.sendStatus(204) 121 return res.sendStatus(204)
@@ -123,9 +124,9 @@ async function acceptOwnership (req: express.Request, res: express.Response) {
123 124
124async function refuseOwnership (req: express.Request, res: express.Response) { 125async function refuseOwnership (req: express.Request, res: express.Response) {
125 return sequelizeTypescript.transaction(async t => { 126 return sequelizeTypescript.transaction(async t => {
126 const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel 127 const videoChangeOwnership = res.locals.videoChangeOwnership
127 128
128 videoChangeOwnership.set('status', VideoChangeOwnershipStatus.REFUSED) 129 videoChangeOwnership.status = VideoChangeOwnershipStatus.REFUSED
129 await videoChangeOwnership.save({ transaction: t }) 130 await videoChangeOwnership.save({ transaction: t })
130 131
131 return res.sendStatus(204) 132 return res.sendStatus(204)
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts
index 53952a0a2..b65babedf 100644
--- a/server/controllers/api/videos/rate.ts
+++ b/server/controllers/api/videos/rate.ts
@@ -1,12 +1,12 @@
1import * as express from 'express' 1import * as express from 'express'
2import { UserVideoRateUpdate } from '../../../../shared' 2import { UserVideoRateUpdate } from '../../../../shared'
3import { logger } from '../../../helpers/logger' 3import { logger } from '../../../helpers/logger'
4import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers' 4import { VIDEO_RATE_TYPES } from '../../../initializers/constants'
5import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub' 5import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub'
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'
9import { VideoModel } from '../../../models/video/video' 9import { sequelizeTypescript } from '../../../initializers/database'
10 10
11const rateVideoRouter = express.Router() 11const rateVideoRouter = express.Router()
12 12
@@ -27,8 +27,8 @@ export {
27async function rateVideo (req: express.Request, res: express.Response) { 27async function rateVideo (req: express.Request, res: express.Response) {
28 const body: UserVideoRateUpdate = req.body 28 const body: UserVideoRateUpdate = req.body
29 const rateType = body.rating 29 const rateType = body.rating
30 const videoInstance: VideoModel = res.locals.video 30 const videoInstance = res.locals.video
31 const userAccount: AccountModel = res.locals.oauth.token.User.Account 31 const userAccount = res.locals.oauth.token.User.Account
32 32
33 await sequelizeTypescript.transaction(async t => { 33 await sequelizeTypescript.transaction(async t => {
34 const sequelizeOptions = { transaction: t } 34 const sequelizeOptions = { transaction: t }
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts
index e8876b47a..dcd1f070d 100644
--- a/server/controllers/api/videos/watching.ts
+++ b/server/controllers/api/videos/watching.ts
@@ -2,7 +2,6 @@ import * as express from 'express'
2import { UserWatchingVideo } from '../../../../shared' 2import { UserWatchingVideo } from '../../../../shared'
3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares' 3import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares'
4import { UserVideoHistoryModel } from '../../../models/account/user-video-history' 4import { UserVideoHistoryModel } from '../../../models/account/user-video-history'
5import { UserModel } from '../../../models/account/user'
6 5
7const watchingRouter = express.Router() 6const watchingRouter = express.Router()
8 7
@@ -21,7 +20,7 @@ export {
21// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
22 21
23async function userWatchVideo (req: express.Request, res: express.Response) { 22async function userWatchVideo (req: express.Request, res: express.Response) {
24 const user = res.locals.oauth.token.User as UserModel 23 const user = res.locals.oauth.token.User
25 24
26 const body: UserWatchingVideo = req.body 25 const body: UserWatchingVideo = req.body
27 const { id: videoId } = res.locals.video as { id: number } 26 const { id: videoId } = res.locals.video as { id: number }
diff --git a/server/controllers/bots.ts b/server/controllers/bots.ts
index 2db86a2d8..e25d9c21b 100644
--- a/server/controllers/bots.ts
+++ b/server/controllers/bots.ts
@@ -1,6 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { asyncMiddleware } from '../middlewares' 2import { asyncMiddleware } from '../middlewares'
3import { CONFIG, ROUTE_CACHE_LIFETIME } from '../initializers' 3import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants'
4import * as sitemapModule from 'sitemap' 4import * as sitemapModule from 'sitemap'
5import { logger } from '../helpers/logger' 5import { logger } from '../helpers/logger'
6import { VideoModel } from '../models/video/video' 6import { VideoModel } from '../models/video/video'
@@ -35,7 +35,7 @@ async function getSitemap (req: express.Request, res: express.Response) {
35 urls = urls.concat(await getSitemapAccountUrls()) 35 urls = urls.concat(await getSitemapAccountUrls())
36 36
37 const sitemap = sitemapModule.createSitemap({ 37 const sitemap = sitemapModule.createSitemap({
38 hostname: CONFIG.WEBSERVER.URL, 38 hostname: WEBSERVER.URL,
39 urls: urls 39 urls: urls
40 }) 40 })
41 41
@@ -54,7 +54,7 @@ async function getSitemapVideoChannelUrls () {
54 const rows = await VideoChannelModel.listLocalsForSitemap('createdAt') 54 const rows = await VideoChannelModel.listLocalsForSitemap('createdAt')
55 55
56 return rows.map(channel => ({ 56 return rows.map(channel => ({
57 url: CONFIG.WEBSERVER.URL + '/video-channels/' + channel.Actor.preferredUsername 57 url: WEBSERVER.URL + '/video-channels/' + channel.Actor.preferredUsername
58 })) 58 }))
59} 59}
60 60
@@ -62,7 +62,7 @@ async function getSitemapAccountUrls () {
62 const rows = await AccountModel.listLocalsForSitemap('createdAt') 62 const rows = await AccountModel.listLocalsForSitemap('createdAt')
63 63
64 return rows.map(channel => ({ 64 return rows.map(channel => ({
65 url: CONFIG.WEBSERVER.URL + '/accounts/' + channel.Actor.preferredUsername 65 url: WEBSERVER.URL + '/accounts/' + channel.Actor.preferredUsername
66 })) 66 }))
67} 67}
68 68
@@ -78,14 +78,14 @@ async function getSitemapLocalVideoUrls () {
78 }) 78 })
79 79
80 return resultList.data.map(v => ({ 80 return resultList.data.map(v => ({
81 url: CONFIG.WEBSERVER.URL + '/videos/watch/' + v.uuid, 81 url: WEBSERVER.URL + '/videos/watch/' + v.uuid,
82 video: [ 82 video: [
83 { 83 {
84 title: v.name, 84 title: v.name,
85 // Sitemap description should be < 2000 characters 85 // Sitemap description should be < 2000 characters
86 description: truncate(v.description || v.name, { length: 2000, omission: '...' }), 86 description: truncate(v.description || v.name, { length: 2000, omission: '...' }),
87 player_loc: CONFIG.WEBSERVER.URL + '/videos/embed/' + v.uuid, 87 player_loc: WEBSERVER.URL + '/videos/embed/' + v.uuid,
88 thumbnail_loc: CONFIG.WEBSERVER.URL + v.getThumbnailStaticPath() 88 thumbnail_loc: WEBSERVER.URL + v.getMiniatureStaticPath()
89 } 89 }
90 ] 90 ]
91 })) 91 }))
@@ -97,5 +97,5 @@ function getSitemapBasicUrls () {
97 '/videos/local' 97 '/videos/local'
98 ] 98 ]
99 99
100 return paths.map(p => ({ url: CONFIG.WEBSERVER.URL + p })) 100 return paths.map(p => ({ url: WEBSERVER.URL + p }))
101} 101}
diff --git a/server/controllers/client.ts b/server/controllers/client.ts
index f17f2a5d2..f51470b41 100644
--- a/server/controllers/client.ts
+++ b/server/controllers/client.ts
@@ -1,7 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { join } from 'path' 2import { join } from 'path'
3import { root } from '../helpers/core-utils' 3import { root } from '../helpers/core-utils'
4import { ACCEPT_HEADERS, STATIC_MAX_AGE } from '../initializers' 4import { ACCEPT_HEADERS, STATIC_MAX_AGE } from '../initializers/constants'
5import { asyncMiddleware, embedCSP } from '../middlewares' 5import { asyncMiddleware, embedCSP } from '../middlewares'
6import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '../../shared/models/i18n/i18n' 6import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '../../shared/models/i18n/i18n'
7import { ClientHtml } from '../lib/client-html' 7import { ClientHtml } from '../lib/client-html'
@@ -17,6 +17,8 @@ const testEmbedPath = join(distPath, 'standalone', 'videos', 'test-embed.html')
17// Special route that add OpenGraph and oEmbed tags 17// Special route that add OpenGraph and oEmbed tags
18// Do not use a template engine for a so little thing 18// Do not use a template engine for a so little thing
19clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage)) 19clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage))
20clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage))
21clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage))
20 22
21clientsRouter.use( 23clientsRouter.use(
22 '/videos/embed', 24 '/videos/embed',
@@ -99,6 +101,18 @@ async function generateWatchHtmlPage (req: express.Request, res: express.Respons
99 return sendHTML(html, res) 101 return sendHTML(html, res)
100} 102}
101 103
104async function generateAccountHtmlPage (req: express.Request, res: express.Response) {
105 const html = await ClientHtml.getAccountHTMLPage(req.params.nameWithHost, req, res)
106
107 return sendHTML(html, res)
108}
109
110async function generateVideoChannelHtmlPage (req: express.Request, res: express.Response) {
111 const html = await ClientHtml.getVideoChannelHTMLPage(req.params.nameWithHost, req, res)
112
113 return sendHTML(html, res)
114}
115
102function sendHTML (html: string, res: express.Response) { 116function sendHTML (html: string, res: express.Response) {
103 res.set('Content-Type', 'text/html; charset=UTF-8') 117 res.set('Content-Type', 'text/html; charset=UTF-8')
104 118
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts
index 960085af1..d3f581615 100644
--- a/server/controllers/feeds.ts
+++ b/server/controllers/feeds.ts
@@ -1,6 +1,5 @@
1import * as express from 'express' 1import * as express from 'express'
2import { CONFIG, FEEDS, ROUTE_CACHE_LIFETIME } from '../initializers/constants' 2import { FEEDS, ROUTE_CACHE_LIFETIME, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants'
3import { THUMBNAILS_SIZE } from '../initializers'
4import { 3import {
5 asyncMiddleware, 4 asyncMiddleware,
6 commonVideosFiltersValidator, 5 commonVideosFiltersValidator,
@@ -11,11 +10,10 @@ import {
11} from '../middlewares' 10} from '../middlewares'
12import { VideoModel } from '../models/video/video' 11import { VideoModel } from '../models/video/video'
13import * as Feed from 'pfeed' 12import * as Feed from 'pfeed'
14import { AccountModel } from '../models/account/account'
15import { cacheRoute } from '../middlewares/cache' 13import { cacheRoute } from '../middlewares/cache'
16import { VideoChannelModel } from '../models/video/video-channel'
17import { VideoCommentModel } from '../models/video/video-comment' 14import { VideoCommentModel } from '../models/video/video-comment'
18import { buildNSFWFilter } from '../helpers/express-utils' 15import { buildNSFWFilter } from '../helpers/express-utils'
16import { CONFIG } from '../initializers/config'
19 17
20const feedsRouter = express.Router() 18const feedsRouter = express.Router()
21 19
@@ -42,10 +40,10 @@ export {
42 40
43// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
44 42
45async function generateVideoCommentsFeed (req: express.Request, res: express.Response, next: express.NextFunction) { 43async function generateVideoCommentsFeed (req: express.Request, res: express.Response) {
46 const start = 0 44 const start = 0
47 45
48 const video = res.locals.video as VideoModel 46 const video = res.locals.video
49 const videoId: number = video ? video.id : undefined 47 const videoId: number = video ? video.id : undefined
50 48
51 const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) 49 const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId)
@@ -56,7 +54,7 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res
56 54
57 // Adding video items to the feed, one at a time 55 // Adding video items to the feed, one at a time
58 comments.forEach(comment => { 56 comments.forEach(comment => {
59 const link = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath() 57 const link = WEBSERVER.URL + comment.getCommentStaticPath()
60 58
61 feed.addItem({ 59 feed.addItem({
62 title: `${comment.Video.name} - ${comment.Account.getDisplayName()}`, 60 title: `${comment.Video.name} - ${comment.Account.getDisplayName()}`,
@@ -77,11 +75,11 @@ async function generateVideoCommentsFeed (req: express.Request, res: express.Res
77 return sendFeed(feed, req, res) 75 return sendFeed(feed, req, res)
78} 76}
79 77
80async function generateVideoFeed (req: express.Request, res: express.Response, next: express.NextFunction) { 78async function generateVideoFeed (req: express.Request, res: express.Response) {
81 const start = 0 79 const start = 0
82 80
83 const account: AccountModel = res.locals.account 81 const account = res.locals.account
84 const videoChannel: VideoChannelModel = res.locals.videoChannel 82 const videoChannel = res.locals.videoChannel
85 const nsfw = buildNSFWFilter(res, req.query.nsfw) 83 const nsfw = buildNSFWFilter(res, req.query.nsfw)
86 84
87 let name: string 85 let name: string
@@ -124,7 +122,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response, n
124 feed.addItem({ 122 feed.addItem({
125 title: video.name, 123 title: video.name,
126 id: video.url, 124 id: video.url,
127 link: CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid, 125 link: WEBSERVER.URL + '/videos/watch/' + video.uuid,
128 description: video.getTruncatedDescription(), 126 description: video.getTruncatedDescription(),
129 content: video.description, 127 content: video.description,
130 author: [ 128 author: [
@@ -139,7 +137,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response, n
139 torrent: torrents, 137 torrent: torrents,
140 thumbnail: [ 138 thumbnail: [
141 { 139 {
142 url: CONFIG.WEBSERVER.URL + video.getThumbnailStaticPath(), 140 url: WEBSERVER.URL + video.getMiniatureStaticPath(),
143 height: THUMBNAILS_SIZE.height, 141 height: THUMBNAILS_SIZE.height,
144 width: THUMBNAILS_SIZE.width 142 width: THUMBNAILS_SIZE.width
145 } 143 }
@@ -152,7 +150,7 @@ async function generateVideoFeed (req: express.Request, res: express.Response, n
152} 150}
153 151
154function initFeed (name: string, description: string) { 152function initFeed (name: string, description: string) {
155 const webserverUrl = CONFIG.WEBSERVER.URL 153 const webserverUrl = WEBSERVER.URL
156 154
157 return new Feed({ 155 return new Feed({
158 title: name, 156 title: name,
diff --git a/server/controllers/services.ts b/server/controllers/services.ts
index 352d0b19a..c1c53c3fc 100644
--- a/server/controllers/services.ts
+++ b/server/controllers/services.ts
@@ -1,8 +1,7 @@
1import * as express from 'express' 1import * as express from 'express'
2import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers' 2import { EMBED_SIZE, PREVIEWS_SIZE, WEBSERVER } from '../initializers/constants'
3import { asyncMiddleware, oembedValidator } from '../middlewares' 3import { asyncMiddleware, oembedValidator } from '../middlewares'
4import { accountsNameWithHostGetValidator } from '../middlewares/validators' 4import { accountNameWithHostGetValidator } from '../middlewares/validators'
5import { VideoModel } from '../models/video/video'
6 5
7const servicesRouter = express.Router() 6const servicesRouter = express.Router()
8 7
@@ -11,7 +10,7 @@ servicesRouter.use('/oembed',
11 generateOEmbed 10 generateOEmbed
12) 11)
13servicesRouter.use('/redirect/accounts/:accountName', 12servicesRouter.use('/redirect/accounts/:accountName',
14 asyncMiddleware(accountsNameWithHostGetValidator), 13 asyncMiddleware(accountNameWithHostGetValidator),
15 redirectToAccountUrl 14 redirectToAccountUrl
16) 15)
17 16
@@ -23,9 +22,9 @@ export {
23 22
24// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
25 24
26function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) { 25function generateOEmbed (req: express.Request, res: express.Response) {
27 const video = res.locals.video as VideoModel 26 const video = res.locals.video
28 const webserverUrl = CONFIG.WEBSERVER.URL 27 const webserverUrl = WEBSERVER.URL
29 const maxHeight = parseInt(req.query.maxheight, 10) 28 const maxHeight = parseInt(req.query.maxheight, 10)
30 const maxWidth = parseInt(req.query.maxwidth, 10) 29 const maxWidth = parseInt(req.query.maxwidth, 10)
31 30
diff --git a/server/controllers/static.ts b/server/controllers/static.ts
index 4fd58f70c..05019fcc2 100644
--- a/server/controllers/static.ts
+++ b/server/controllers/static.ts
@@ -1,16 +1,23 @@
1import * as cors from 'cors' 1import * as cors from 'cors'
2import * as express from 'express' 2import * as express from 'express'
3import { CONFIG, ROUTE_CACHE_LIFETIME, STATIC_DOWNLOAD_PATHS, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers' 3import {
4import { VideosPreviewCache } from '../lib/cache' 4 HLS_STREAMING_PLAYLIST_DIRECTORY,
5 ROUTE_CACHE_LIFETIME,
6 STATIC_DOWNLOAD_PATHS,
7 STATIC_MAX_AGE,
8 STATIC_PATHS,
9 WEBSERVER
10} from '../initializers/constants'
11import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache'
5import { cacheRoute } from '../middlewares/cache' 12import { cacheRoute } from '../middlewares/cache'
6import { asyncMiddleware, videosGetValidator } from '../middlewares' 13import { asyncMiddleware, videosGetValidator } from '../middlewares'
7import { VideoModel } from '../models/video/video' 14import { VideoModel } from '../models/video/video'
8import { VideosCaptionCache } from '../lib/cache/videos-caption-cache'
9import { UserModel } from '../models/account/user' 15import { UserModel } from '../models/account/user'
10import { VideoCommentModel } from '../models/video/video-comment' 16import { VideoCommentModel } from '../models/video/video-comment'
11import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' 17import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo'
12import { join } from 'path' 18import { join } from 'path'
13import { root } from '../helpers/core-utils' 19import { root } from '../helpers/core-utils'
20import { CONFIG } from '../initializers/config'
14 21
15const packageJSON = require('../../../package.json') 22const packageJSON = require('../../../package.json')
16const staticRouter = express.Router() 23const staticRouter = express.Router()
@@ -51,6 +58,13 @@ staticRouter.use(
51 asyncMiddleware(downloadVideoFile) 58 asyncMiddleware(downloadVideoFile)
52) 59)
53 60
61// HLS
62staticRouter.use(
63 STATIC_PATHS.STREAMING_PLAYLISTS.HLS,
64 cors(),
65 express.static(HLS_STREAMING_PLAYLIST_DIRECTORY, { fallthrough: false }) // 404 if the file does not exist
66)
67
54// Thumbnails path for express 68// Thumbnails path for express
55const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR 69const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR
56staticRouter.use( 70staticRouter.use(
@@ -108,7 +122,7 @@ staticRouter.use('/.well-known/nodeinfo',
108 links: [ 122 links: [
109 { 123 {
110 rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0', 124 rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
111 href: CONFIG.WEBSERVER.URL + '/nodeinfo/2.0.json' 125 href: WEBSERVER.URL + '/nodeinfo/2.0.json'
112 } 126 }
113 ] 127 ]
114 }) 128 })
@@ -150,21 +164,21 @@ export {
150 164
151// --------------------------------------------------------------------------- 165// ---------------------------------------------------------------------------
152 166
153async function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) { 167async function getPreview (req: express.Request, res: express.Response) {
154 const path = await VideosPreviewCache.Instance.getFilePath(req.params.uuid) 168 const result = await VideosPreviewCache.Instance.getFilePath(req.params.uuid)
155 if (!path) return res.sendStatus(404) 169 if (!result) return res.sendStatus(404)
156 170
157 return res.sendFile(path, { maxAge: STATIC_MAX_AGE }) 171 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
158} 172}
159 173
160async function getVideoCaption (req: express.Request, res: express.Response) { 174async function getVideoCaption (req: express.Request, res: express.Response) {
161 const path = await VideosCaptionCache.Instance.getFilePath({ 175 const result = await VideosCaptionCache.Instance.getFilePath({
162 videoId: req.params.videoId, 176 videoId: req.params.videoId,
163 language: req.params.captionLanguage 177 language: req.params.captionLanguage
164 }) 178 })
165 if (!path) return res.sendStatus(404) 179 if (!result) return res.sendStatus(404)
166 180
167 return res.sendFile(path, { maxAge: STATIC_MAX_AGE }) 181 return res.sendFile(result.path, { maxAge: STATIC_MAX_AGE })
168} 182}
169 183
170async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) { 184async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) {
@@ -231,7 +245,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response, n
231 245
232function getVideoAndFile (req: express.Request, res: express.Response) { 246function getVideoAndFile (req: express.Request, res: express.Response) {
233 const resolution = parseInt(req.params.resolution, 10) 247 const resolution = parseInt(req.params.resolution, 10)
234 const video: VideoModel = res.locals.video 248 const video = res.locals.video
235 249
236 const videoFile = video.VideoFiles.find(f => f.resolution === resolution) 250 const videoFile = video.VideoFiles.find(f => f.resolution === resolution)
237 251
diff --git a/server/controllers/tracker.ts b/server/controllers/tracker.ts
index 1deb8c402..912f82b86 100644
--- a/server/controllers/tracker.ts
+++ b/server/controllers/tracker.ts
@@ -4,9 +4,11 @@ import * as http from 'http'
4import * as bitTorrentTracker from 'bittorrent-tracker' 4import * as bitTorrentTracker from 'bittorrent-tracker'
5import * as proxyAddr from 'proxy-addr' 5import * as proxyAddr from 'proxy-addr'
6import { Server as WebSocketServer } from 'ws' 6import { Server as WebSocketServer } from 'ws'
7import { CONFIG, 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' 9import { parse } from 'url'
10import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist'
11import { CONFIG } from '../initializers/config'
10 12
11const TrackerServer = bitTorrentTracker.Server 13const TrackerServer = bitTorrentTracker.Server
12 14
@@ -21,7 +23,11 @@ const trackerServer = new TrackerServer({
21 udp: false, 23 udp: false,
22 ws: false, 24 ws: false,
23 dht: false, 25 dht: false,
24 filter: function (infoHash, params, cb) { 26 filter: async function (infoHash, params, cb) {
27 if (CONFIG.TRACKER.ENABLED === false) {
28 return cb(new Error('Tracker is disabled on this instance.'))
29 }
30
25 let ip: string 31 let ip: string
26 32
27 if (params.type === 'ws') { 33 if (params.type === 'ws') {
@@ -32,29 +38,40 @@ const trackerServer = new TrackerServer({
32 38
33 const key = ip + '-' + infoHash 39 const key = ip + '-' + infoHash
34 40
35 peersIps[ip] = peersIps[ip] ? peersIps[ip] + 1 : 1 41 peersIps[ ip ] = peersIps[ ip ] ? peersIps[ ip ] + 1 : 1
36 peersIpInfoHash[key] = peersIpInfoHash[key] ? peersIpInfoHash[key] + 1 : 1 42 peersIpInfoHash[ key ] = peersIpInfoHash[ key ] ? peersIpInfoHash[ key ] + 1 : 1
37 43
38 if (peersIpInfoHash[key] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) { 44 if (CONFIG.TRACKER.REJECT_TOO_MANY_ANNOUNCES && peersIpInfoHash[ key ] > TRACKER_RATE_LIMITS.ANNOUNCES_PER_IP_PER_INFOHASH) {
39 return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`)) 45 return cb(new Error(`Too many requests (${peersIpInfoHash[ key ]} of ip ${ip} for torrent ${infoHash}`))
40 } 46 }
41 47
42 VideoFileModel.isInfohashExists(infoHash) 48 try {
43 .then(exists => { 49 if (CONFIG.TRACKER.PRIVATE === false) return cb()
44 if (exists === false) return cb(new Error(`Unknown infoHash ${infoHash}`)) 50
51 const videoFileExists = await VideoFileModel.doesInfohashExist(infoHash)
52 if (videoFileExists === true) return cb()
45 53
46 return cb() 54 const playlistExists = await VideoStreamingPlaylistModel.doesInfohashExist(infoHash)
47 }) 55 if (playlistExists === true) return cb()
56
57 return cb(new Error(`Unknown infoHash ${infoHash}`))
58 } catch (err) {
59 logger.error('Error in tracker filter.', { err })
60 return cb(err)
61 }
48 } 62 }
49}) 63})
50 64
51trackerServer.on('error', function (err) { 65if (CONFIG.TRACKER.ENABLED !== false) {
52 logger.error('Error in tracker.', { err })
53})
54 66
55trackerServer.on('warning', function (err) { 67 trackerServer.on('error', function (err) {
56 logger.warn('Warning in tracker.', { err }) 68 logger.error('Error in tracker.', { err })
57}) 69 })
70
71 trackerServer.on('warning', function (err) {
72 logger.warn('Warning in tracker.', { err })
73 })
74}
58 75
59const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer) 76const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer)
60trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' })) 77trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' }))
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts
index ed781c21b..f2ba3c826 100644
--- a/server/controllers/webfinger.ts
+++ b/server/controllers/webfinger.ts
@@ -1,7 +1,6 @@
1import * as express from 'express' 1import * as express from 'express'
2import { asyncMiddleware } from '../middlewares' 2import { asyncMiddleware } from '../middlewares'
3import { webfingerValidator } from '../middlewares/validators' 3import { webfingerValidator } from '../middlewares/validators'
4import { ActorModel } from '../models/activitypub/actor'
5 4
6const webfingerRouter = express.Router() 5const webfingerRouter = express.Router()
7 6
@@ -18,8 +17,8 @@ export {
18 17
19// --------------------------------------------------------------------------- 18// ---------------------------------------------------------------------------
20 19
21function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) { 20function webfingerController (req: express.Request, res: express.Response) {
22 const actor = res.locals.actor as ActorModel 21 const actor = res.locals.actor
23 22
24 const json = { 23 const json = {
25 subject: req.query.resource, 24 subject: req.query.resource,