diff options
Diffstat (limited to 'server/controllers')
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 @@ | |||
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' | 3 | import { VideoPrivacy, VideoRateType } from '../../../shared/models/videos' |
4 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' | 4 | import { activityPubCollectionPagination, activityPubContextify } from '../../helpers/activitypub' |
5 | import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../initializers' | 5 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../../initializers/constants' |
6 | import { buildAnnounceWithVideoAudience, buildDislikeActivity, buildLikeActivity } from '../../lib/activitypub/send' | 6 | import { buildAnnounceWithVideoAudience, buildLikeActivity } from '../../lib/activitypub/send' |
7 | import { audiencify, getAudience } from '../../lib/activitypub/audience' | 7 | import { audiencify, getAudience } from '../../lib/activitypub/audience' |
8 | import { buildCreateActivity } from '../../lib/activitypub/send/send-create' | 8 | import { buildCreateActivity } from '../../lib/activitypub/send/send-create' |
9 | import { | 9 | import { |
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' |
17 | import { | 17 | import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' |
18 | getAccountVideoRateValidator, | ||
19 | videoCommentGetValidator, | ||
20 | videosGetValidator | ||
21 | } from '../../middlewares/validators' | ||
22 | import { AccountModel } from '../../models/account/account' | 18 | import { AccountModel } from '../../models/account/account' |
23 | import { ActorModel } from '../../models/activitypub/actor' | 19 | import { ActorModel } from '../../models/activitypub/actor' |
24 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 20 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
25 | import { VideoModel } from '../../models/video/video' | 21 | import { VideoModel } from '../../models/video/video' |
26 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
27 | import { VideoCommentModel } from '../../models/video/video-comment' | 22 | import { VideoCommentModel } from '../../models/video/video-comment' |
28 | import { VideoShareModel } from '../../models/video/video-share' | 23 | import { VideoShareModel } from '../../models/video/video-share' |
29 | import { cacheRoute } from '../../middlewares/cache' | 24 | import { cacheRoute } from '../../middlewares/cache' |
@@ -37,87 +32,129 @@ import { | |||
37 | getVideoSharesActivityPubUrl | 32 | getVideoSharesActivityPubUrl |
38 | } from '../../lib/activitypub' | 33 | } from '../../lib/activitypub' |
39 | import { VideoCaptionModel } from '../../models/video/video-caption' | 34 | import { VideoCaptionModel } from '../../models/video/video-caption' |
40 | import { videoRedundancyGetValidator } from '../../middlewares/validators/redundancy' | 35 | import { videoFileRedundancyGetValidator, videoPlaylistRedundancyGetValidator } from '../../middlewares/validators/redundancy' |
41 | import { getServerActor } from '../../helpers/utils' | 36 | import { getServerActor } from '../../helpers/utils' |
42 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 37 | import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' |
38 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' | ||
39 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
40 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
43 | 41 | ||
44 | const activityPubClientRouter = express.Router() | 42 | const activityPubClientRouter = express.Router() |
45 | 43 | ||
46 | activityPubClientRouter.get('/accounts?/:name', | 44 | activityPubClientRouter.get('/accounts?/:name', |
47 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), | 45 | executeIfActivityPub, |
48 | executeIfActivityPub(accountController) | 46 | asyncMiddleware(localAccountValidator), |
47 | accountController | ||
49 | ) | 48 | ) |
50 | activityPubClientRouter.get('/accounts?/:name/followers', | 49 | activityPubClientRouter.get('/accounts?/:name/followers', |
51 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), | 50 | executeIfActivityPub, |
52 | executeIfActivityPub(asyncMiddleware(accountFollowersController)) | 51 | asyncMiddleware(localAccountValidator), |
52 | asyncMiddleware(accountFollowersController) | ||
53 | ) | 53 | ) |
54 | activityPubClientRouter.get('/accounts?/:name/following', | 54 | activityPubClientRouter.get('/accounts?/:name/following', |
55 | executeIfActivityPub(asyncMiddleware(localAccountValidator)), | 55 | executeIfActivityPub, |
56 | executeIfActivityPub(asyncMiddleware(accountFollowingController)) | 56 | asyncMiddleware(localAccountValidator), |
57 | asyncMiddleware(accountFollowingController) | ||
58 | ) | ||
59 | activityPubClientRouter.get('/accounts?/:name/playlists', | ||
60 | executeIfActivityPub, | ||
61 | asyncMiddleware(localAccountValidator), | ||
62 | asyncMiddleware(accountPlaylistsController) | ||
57 | ) | 63 | ) |
58 | activityPubClientRouter.get('/accounts?/:name/likes/:videoId', | 64 | activityPubClientRouter.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 | ) |
62 | activityPubClientRouter.get('/accounts?/:name/dislikes/:videoId', | 69 | activityPubClientRouter.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 | ||
67 | activityPubClientRouter.get('/videos/watch/:id', | 75 | activityPubClientRouter.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 | ) |
72 | activityPubClientRouter.get('/videos/watch/:id/activity', | 81 | activityPubClientRouter.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 | ) |
76 | activityPubClientRouter.get('/videos/watch/:id/announces', | 86 | activityPubClientRouter.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 | ) |
80 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', | 91 | activityPubClientRouter.get('/videos/watch/:id/announces/:actorId', |
81 | executeIfActivityPub(asyncMiddleware(videosShareValidator)), | 92 | executeIfActivityPub, |
82 | executeIfActivityPub(asyncMiddleware(videoAnnounceController)) | 93 | asyncMiddleware(videosShareValidator), |
94 | asyncMiddleware(videoAnnounceController) | ||
83 | ) | 95 | ) |
84 | activityPubClientRouter.get('/videos/watch/:id/likes', | 96 | activityPubClientRouter.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 | ) |
88 | activityPubClientRouter.get('/videos/watch/:id/dislikes', | 101 | activityPubClientRouter.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 | ) |
92 | activityPubClientRouter.get('/videos/watch/:id/comments', | 106 | activityPubClientRouter.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 | ) |
96 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', | 111 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId', |
97 | executeIfActivityPub(asyncMiddleware(videoCommentGetValidator)), | 112 | executeIfActivityPub, |
98 | executeIfActivityPub(asyncMiddleware(videoCommentController)) | 113 | asyncMiddleware(videoCommentGetValidator), |
114 | asyncMiddleware(videoCommentController) | ||
99 | ) | 115 | ) |
100 | activityPubClientRouter.get('/videos/watch/:videoId/comments/:commentId/activity', | 116 | activityPubClientRouter.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 | ||
105 | activityPubClientRouter.get('/video-channels/:name', | 122 | activityPubClientRouter.get('/video-channels/:name', |
106 | executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), | 123 | executeIfActivityPub, |
107 | executeIfActivityPub(asyncMiddleware(videoChannelController)) | 124 | asyncMiddleware(localVideoChannelValidator), |
125 | asyncMiddleware(videoChannelController) | ||
108 | ) | 126 | ) |
109 | activityPubClientRouter.get('/video-channels/:name/followers', | 127 | activityPubClientRouter.get('/video-channels/:name/followers', |
110 | executeIfActivityPub(asyncMiddleware(localVideoChannelValidator)), | 128 | executeIfActivityPub, |
111 | executeIfActivityPub(asyncMiddleware(videoChannelFollowersController)) | 129 | asyncMiddleware(localVideoChannelValidator), |
130 | asyncMiddleware(videoChannelFollowersController) | ||
112 | ) | 131 | ) |
113 | activityPubClientRouter.get('/video-channels/:name/following', | 132 | activityPubClientRouter.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 | ||
118 | activityPubClientRouter.get('/redundancy/videos/:videoId/:resolution([0-9]+)(-:fps([0-9]+))?', | 138 | activityPubClientRouter.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 | ) | ||
143 | activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistType/:videoId', | ||
144 | executeIfActivityPub, | ||
145 | asyncMiddleware(videoPlaylistRedundancyGetValidator), | ||
146 | asyncMiddleware(videoRedundancyController) | ||
147 | ) | ||
148 | |||
149 | activityPubClientRouter.get('/video-playlists/:playlistId', | ||
150 | executeIfActivityPub, | ||
151 | asyncMiddleware(videoPlaylistsGetValidator), | ||
152 | asyncMiddleware(videoPlaylistController) | ||
153 | ) | ||
154 | activityPubClientRouter.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 | ||
131 | function accountController (req: express.Request, res: express.Response, next: express.NextFunction) { | 168 | function 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 | ||
137 | async function accountFollowersController (req: express.Request, res: express.Response, next: express.NextFunction) { | 174 | async 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 | ||
144 | async function accountFollowingController (req: express.Request, res: express.Response, next: express.NextFunction) { | 181 | async 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 | ||
188 | async 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 | |||
151 | function getAccountVideoRate (rateType: VideoRateType) { | 195 | function 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 | ||
165 | async function videoController (req: express.Request, res: express.Response) { | 209 | async 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 | ||
184 | async function videoAnnounceController (req: express.Request, res: express.Response) { | 229 | async 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 | ||
194 | async function videoAnnouncesController (req: express.Request, res: express.Response) { | 239 | async 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 | ||
209 | async function videoLikesController (req: express.Request, res: express.Response) { | 254 | async 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 | ||
216 | async function videoDislikesController (req: express.Request, res: express.Response) { | 261 | async 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 | ||
223 | async function videoCommentsController (req: express.Request, res: express.Response) { | 268 | async 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 | ||
238 | async function videoChannelController (req: express.Request, res: express.Response) { | 283 | async 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 | ||
244 | async function videoChannelFollowersController (req: express.Request, res: express.Response) { | 289 | async 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 | ||
251 | async function videoChannelFollowingController (req: express.Request, res: express.Response) { | 296 | async 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 | ||
258 | async function videoCommentController (req: express.Request, res: express.Response) { | 303 | async 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 | ||
277 | async function videoRedundancyController (req: express.Request, res: express.Response) { | 322 | async 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 | ||
339 | async 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 | |||
352 | async 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 | ||
296 | async function actorFollowing (req: express.Request, actor: ActorModel) { | 361 | async 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 | ||
304 | async function actorFollowers (req: express.Request, actor: ActorModel) { | 369 | async 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 | |||
377 | async 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 | ||
312 | function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) { | 385 | function 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' | |||
5 | import { processActivities } from '../../lib/activitypub/process/process' | 5 | import { processActivities } from '../../lib/activitypub/process/process' |
6 | import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChannelValidator, signatureValidator } from '../../middlewares' | 6 | import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChannelValidator, signatureValidator } from '../../middlewares' |
7 | import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' | 7 | import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' |
8 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
9 | import { AccountModel } from '../../models/account/account' | ||
10 | import { queue } from 'async' | 8 | import { queue } from 'async' |
11 | import { ActorModel } from '../../models/activitypub/actor' | 9 | import { 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 | ||
35 | async function outboxController (req: express.Request, res: express.Response, next: express.NextFunction) { | 35 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { getFormattedObjects } from '../../helpers/utils' | 2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' |
3 | import { | 3 | import { |
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' |
12 | import { accountsNameWithHostGetValidator, accountsSortValidator, videosSortValidator } from '../../middlewares/validators' | 15 | import { |
16 | accountNameWithHostGetValidator, | ||
17 | accountsSortValidator, | ||
18 | ensureAuthUserOwnsAccountValidator, | ||
19 | videosSortValidator | ||
20 | } from '../../middlewares/validators' | ||
13 | import { AccountModel } from '../../models/account/account' | 21 | import { AccountModel } from '../../models/account/account' |
22 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | ||
14 | import { VideoModel } from '../../models/video/video' | 23 | import { VideoModel } from '../../models/video/video' |
15 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 24 | import { buildNSFWFilter, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
16 | import { VideoChannelModel } from '../../models/video/video-channel' | 25 | import { VideoChannelModel } from '../../models/video/video-channel' |
17 | import { JobQueue } from '../../lib/job-queue' | 26 | import { JobQueue } from '../../lib/job-queue' |
18 | import { logger } from '../../helpers/logger' | 27 | import { logger } from '../../helpers/logger' |
28 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
29 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' | ||
19 | 30 | ||
20 | const accountsRouter = express.Router() | 31 | const accountsRouter = express.Router() |
21 | 32 | ||
@@ -28,12 +39,12 @@ accountsRouter.get('/', | |||
28 | ) | 39 | ) |
29 | 40 | ||
30 | accountsRouter.get('/:accountName', | 41 | accountsRouter.get('/:accountName', |
31 | asyncMiddleware(accountsNameWithHostGetValidator), | 42 | asyncMiddleware(accountNameWithHostGetValidator), |
32 | getAccount | 43 | getAccount |
33 | ) | 44 | ) |
34 | 45 | ||
35 | accountsRouter.get('/:accountName/videos', | 46 | accountsRouter.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 | ||
46 | accountsRouter.get('/:accountName/video-channels', | 57 | accountsRouter.get('/:accountName/video-channels', |
47 | asyncMiddleware(listVideoAccountChannelsValidator), | 58 | asyncMiddleware(accountNameWithHostGetValidator), |
48 | asyncMiddleware(listVideoAccountChannels) | 59 | asyncMiddleware(listAccountChannels) |
60 | ) | ||
61 | |||
62 | accountsRouter.get('/:accountName/video-playlists', | ||
63 | optionalAuthenticate, | ||
64 | asyncMiddleware(accountNameWithHostGetValidator), | ||
65 | paginationValidator, | ||
66 | videoPlaylistsSortValidator, | ||
67 | setDefaultSort, | ||
68 | setDefaultPagination, | ||
69 | commonVideoPlaylistFiltersValidator, | ||
70 | asyncMiddleware(listAccountPlaylists) | ||
71 | ) | ||
72 | |||
73 | accountsRouter.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 | ||
59 | function getAccount (req: express.Request, res: express.Response, next: express.NextFunction) { | 93 | function 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 | ||
70 | async function listAccounts (req: express.Request, res: express.Response, next: express.NextFunction) { | 104 | async 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 | ||
76 | async function listVideoAccountChannels (req: express.Request, res: express.Response, next: express.NextFunction) { | 110 | async 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 | ||
82 | async function listAccountVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 116 | async 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 | |||
138 | async 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 | |||
163 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { omit, snakeCase } from 'lodash' | 2 | import { snakeCase } from 'lodash' |
3 | import { ServerConfig, UserRight } from '../../../shared' | 3 | import { ServerConfig, UserRight } from '../../../shared' |
4 | import { About } from '../../../shared/models/server/about.model' | 4 | import { About } from '../../../shared/models/server/about.model' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' |
6 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | 6 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' |
7 | import { CONFIG, CONSTRAINTS_FIELDS, reloadConfig } from '../../initializers' | 7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
8 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' | 8 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../middlewares' |
9 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' | 9 | import { customConfigUpdateValidator } from '../../middlewares/validators/config' |
10 | import { ClientHtml } from '../../lib/client-html' | 10 | import { ClientHtml } from '../../lib/client-html' |
@@ -14,6 +14,7 @@ import { getServerCommit } from '../../helpers/utils' | |||
14 | import { Emailer } from '../../lib/emailer' | 14 | import { Emailer } from '../../lib/emailer' |
15 | import { isNumeric } from 'validator' | 15 | import { isNumeric } from 'validator' |
16 | import { objectConverter } from '../../helpers/core-utils' | 16 | import { objectConverter } from '../../helpers/core-utils' |
17 | import { CONFIG, reloadConfig } from '../../initializers/config' | ||
17 | 18 | ||
18 | const packageJSON = require('../../../../package.json') | 19 | const packageJSON = require('../../../../package.json') |
19 | const configRouter = express.Router() | 20 | const 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 | ||
134 | function getAbout (req: express.Request, res: express.Response, next: express.NextFunction) { | 149 | function 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 | ||
147 | async function getCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | 162 | async 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 | ||
153 | async function deleteCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | 168 | async 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 | ||
166 | async function updateCustomConfig (req: express.Request, res: express.Response, next: express.NextFunction) { | 181 | async 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' | |||
11 | import * as cors from 'cors' | 11 | import * as cors from 'cors' |
12 | import { searchRouter } from './search' | 12 | import { searchRouter } from './search' |
13 | import { overviewsRouter } from './overviews' | 13 | import { overviewsRouter } from './overviews' |
14 | import { videoPlaylistRouter } from './video-playlist' | ||
14 | 15 | ||
15 | const apiRouter = express.Router() | 16 | const apiRouter = express.Router() |
16 | 17 | ||
@@ -26,6 +27,7 @@ apiRouter.use('/config', configRouter) | |||
26 | apiRouter.use('/users', usersRouter) | 27 | apiRouter.use('/users', usersRouter) |
27 | apiRouter.use('/accounts', accountsRouter) | 28 | apiRouter.use('/accounts', accountsRouter) |
28 | apiRouter.use('/video-channels', videoChannelRouter) | 29 | apiRouter.use('/video-channels', videoChannelRouter) |
30 | apiRouter.use('/video-playlists', videoPlaylistRouter) | ||
29 | apiRouter.use('/videos', videosRouter) | 31 | apiRouter.use('/videos', videosRouter) |
30 | apiRouter.use('/jobs', jobsRouter) | 32 | apiRouter.use('/jobs', jobsRouter) |
31 | apiRouter.use('/search', searchRouter) | 33 | apiRouter.use('/search', searchRouter) |
@@ -39,6 +41,6 @@ export { apiRouter } | |||
39 | 41 | ||
40 | // --------------------------------------------------------------------------- | 42 | // --------------------------------------------------------------------------- |
41 | 43 | ||
42 | function pong (req: express.Request, res: express.Response, next: express.NextFunction) { | 44 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { OAuthClientLocal } from '../../../shared' | 2 | import { OAuthClientLocal } from '../../../shared' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { CONFIG } from '../../initializers' | 4 | import { CONFIG } from '../../initializers/config' |
5 | import { asyncMiddleware } from '../../middlewares' | 5 | import { asyncMiddleware } from '../../middlewares' |
6 | import { OAuthClientModel } from '../../models/oauth/oauth-client' | 6 | import { 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' | |||
4 | import { asyncMiddleware } from '../../middlewares' | 4 | import { asyncMiddleware } from '../../middlewares' |
5 | import { TagModel } from '../../models/video/tag' | 5 | import { TagModel } from '../../models/video/tag' |
6 | import { VideosOverview } from '../../../shared/models/overviews' | 6 | import { VideosOverview } from '../../../shared/models/overviews' |
7 | import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers' | 7 | import { MEMOIZE_TTL, OVERVIEWS, ROUTE_CACHE_LIFETIME } from '../../initializers/constants' |
8 | import { cacheRoute } from '../../middlewares/cache' | 8 | import { cacheRoute } from '../../middlewares/cache' |
9 | import * as memoizee from 'memoizee' | 9 | import * 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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { UserRight } from '../../../../shared/models/users' | ||
3 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' | ||
4 | |||
5 | const debugRouter = express.Router() | ||
6 | |||
7 | debugRouter.get('/debug', | ||
8 | authenticate, | ||
9 | ensureUserHasRight(UserRight.MANAGE_DEBUG), | ||
10 | asyncMiddleware(getDebug) | ||
11 | ) | ||
12 | |||
13 | // --------------------------------------------------------------------------- | ||
14 | |||
15 | export { | ||
16 | debugRouter | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | async 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' | |||
2 | import { UserRight } from '../../../../shared/models/users' | 2 | import { UserRight } from '../../../../shared/models/users' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' | 4 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' |
5 | import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../../../initializers' | 5 | import { SERVER_ACTOR_NAME } from '../../../initializers/constants' |
6 | import { sendUndoFollow } from '../../../lib/activitypub/send' | 6 | import { sendAccept, sendReject, sendUndoFollow } from '../../../lib/activitypub/send' |
7 | import { | 7 | import { |
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' |
17 | import { followersSortValidator, followingSortValidator, followValidator } from '../../../middlewares/validators' | 16 | import { |
17 | acceptOrRejectFollowerValidator, | ||
18 | followersSortValidator, | ||
19 | followingSortValidator, | ||
20 | followValidator, | ||
21 | getFollowerValidator, | ||
22 | removeFollowingValidator | ||
23 | } from '../../../middlewares/validators' | ||
18 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 24 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
19 | import { JobQueue } from '../../../lib/job-queue' | 25 | import { JobQueue } from '../../../lib/job-queue' |
20 | import { removeRedundancyOf } from '../../../lib/redundancy' | 26 | import { removeRedundancyOf } from '../../../lib/redundancy' |
27 | import { sequelizeTypescript } from '../../../initializers/database' | ||
21 | 28 | ||
22 | const serverFollowsRouter = express.Router() | 29 | const serverFollowsRouter = express.Router() |
23 | serverFollowsRouter.get('/following', | 30 | serverFollowsRouter.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 | ||
46 | serverFollowsRouter.get('/followers', | 53 | serverFollowsRouter.get('/followers', |
@@ -51,6 +58,29 @@ serverFollowsRouter.get('/followers', | |||
51 | asyncMiddleware(listFollowers) | 58 | asyncMiddleware(listFollowers) |
52 | ) | 59 | ) |
53 | 60 | ||
61 | serverFollowsRouter.delete('/followers/:nameWithHost', | ||
62 | authenticate, | ||
63 | ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), | ||
64 | asyncMiddleware(getFollowerValidator), | ||
65 | asyncMiddleware(removeOrRejectFollower) | ||
66 | ) | ||
67 | |||
68 | serverFollowsRouter.post('/followers/:nameWithHost/reject', | ||
69 | authenticate, | ||
70 | ensureUserHasRight(UserRight.MANAGE_SERVER_FOLLOW), | ||
71 | asyncMiddleware(getFollowerValidator), | ||
72 | acceptOrRejectFollowerValidator, | ||
73 | asyncMiddleware(removeOrRejectFollower) | ||
74 | ) | ||
75 | |||
76 | serverFollowsRouter.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 | ||
56 | export { | 86 | export { |
@@ -59,7 +89,7 @@ export { | |||
59 | 89 | ||
60 | // --------------------------------------------------------------------------- | 90 | // --------------------------------------------------------------------------- |
61 | 91 | ||
62 | async function listFollowing (req: express.Request, res: express.Response, next: express.NextFunction) { | 92 | async 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 | ||
75 | async function listFollowers (req: express.Request, res: express.Response, next: express.NextFunction) { | 105 | async 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 | ||
88 | async function followInstance (req: express.Request, res: express.Response, next: express.NextFunction) { | 118 | async 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 | ||
106 | async function removeFollow (req: express.Request, res: express.Response, next: express.NextFunction) { | 136 | async 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 | |||
157 | async 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 | |||
167 | async 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' | |||
4 | import { serverRedundancyRouter } from './redundancy' | 4 | import { serverRedundancyRouter } from './redundancy' |
5 | import { serverBlocklistRouter } from './server-blocklist' | 5 | import { serverBlocklistRouter } from './server-blocklist' |
6 | import { contactRouter } from './contact' | 6 | import { contactRouter } from './contact' |
7 | import { logsRouter } from './logs' | ||
8 | import { debugRouter } from './debug' | ||
7 | 9 | ||
8 | const serverRouter = express.Router() | 10 | const serverRouter = express.Router() |
9 | 11 | ||
@@ -12,6 +14,8 @@ serverRouter.use('/', serverRedundancyRouter) | |||
12 | serverRouter.use('/', statsRouter) | 14 | serverRouter.use('/', statsRouter) |
13 | serverRouter.use('/', serverBlocklistRouter) | 15 | serverRouter.use('/', serverBlocklistRouter) |
14 | serverRouter.use('/', contactRouter) | 16 | serverRouter.use('/', contactRouter) |
17 | serverRouter.use('/', logsRouter) | ||
18 | serverRouter.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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { UserRight } from '../../../../shared/models/users' | ||
3 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' | ||
4 | import { mtimeSortFilesDesc } from '../../../../shared/core-utils/logs/logs' | ||
5 | import { readdir, readFile } from 'fs-extra' | ||
6 | import { MAX_LOGS_OUTPUT_CHARACTERS } from '../../../initializers/constants' | ||
7 | import { join } from 'path' | ||
8 | import { getLogsValidator } from '../../../middlewares/validators/logs' | ||
9 | import { LogLevel } from '../../../../shared/models/server/log-level.type' | ||
10 | import { CONFIG } from '../../../initializers/config' | ||
11 | |||
12 | const logsRouter = express.Router() | ||
13 | |||
14 | logsRouter.get('/logs', | ||
15 | authenticate, | ||
16 | ensureUserHasRight(UserRight.MANAGE_LOGS), | ||
17 | getLogsValidator, | ||
18 | asyncMiddleware(getLogs) | ||
19 | ) | ||
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | export { | ||
24 | logsRouter | ||
25 | } | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | async 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 | |||
55 | async 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' | |||
2 | import { UserRight } from '../../../../shared/models/users' | 2 | import { UserRight } from '../../../../shared/models/users' |
3 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' | 3 | import { asyncMiddleware, authenticate, ensureUserHasRight } from '../../../middlewares' |
4 | import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy' | 4 | import { updateServerRedundancyValidator } from '../../../middlewares/validators/redundancy' |
5 | import { ServerModel } from '../../../models/server/server' | ||
6 | import { removeRedundancyOf } from '../../../lib/redundancy' | 5 | import { removeRedundancyOf } from '../../../lib/redundancy' |
7 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
8 | 7 | ||
@@ -23,8 +22,8 @@ export { | |||
23 | 22 | ||
24 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
25 | 24 | ||
26 | async function updateRedundancy (req: express.Request, res: express.Response, next: express.NextFunction) { | 25 | async 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' |
21 | import { AccountModel } from '../../../models/account/account' | ||
22 | import { AccountBlocklistModel } from '../../../models/account/account-blocklist' | 21 | import { AccountBlocklistModel } from '../../../models/account/account-blocklist' |
23 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' | 22 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' |
24 | import { ServerBlocklistModel } from '../../../models/server/server-blocklist' | 23 | import { ServerBlocklistModel } from '../../../models/server/server-blocklist' |
25 | import { ServerModel } from '../../../models/server/server' | ||
26 | import { UserRight } from '../../../../shared/models/users' | 24 | import { UserRight } from '../../../../shared/models/users' |
27 | 25 | ||
28 | const serverBlocklistRouter = express.Router() | 26 | const serverBlocklistRouter = express.Router() |
@@ -91,7 +89,7 @@ async function listBlockedAccounts (req: express.Request, res: express.Response) | |||
91 | 89 | ||
92 | async function blockAccount (req: express.Request, res: express.Response) { | 90 | async 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 | ||
101 | async function unblockAccount (req: express.Request, res: express.Response) { | 99 | async 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 | ||
117 | async function blockServer (req: express.Request, res: express.Response) { | 115 | async 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 | ||
126 | async function unblockServer (req: express.Request, res: express.Response) { | 124 | async 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' | |||
6 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
7 | import { VideoCommentModel } from '../../../models/video/video-comment' | 7 | import { VideoCommentModel } from '../../../models/video/video-comment' |
8 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | 8 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' |
9 | import { CONFIG, ROUTE_CACHE_LIFETIME } from '../../../initializers/constants' | 9 | import { ROUTE_CACHE_LIFETIME } from '../../../initializers/constants' |
10 | import { cacheRoute } from '../../../middlewares/cache' | 10 | import { cacheRoute } from '../../../middlewares/cache' |
11 | import { VideoFileModel } from '../../../models/video/video-file' | 11 | import { VideoFileModel } from '../../../models/video/video-file' |
12 | import { CONFIG } from '../../../initializers/config' | ||
12 | 13 | ||
13 | const statsRouter = express.Router() | 14 | const 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' | |||
3 | import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' | 3 | import { UserCreate, UserRight, UserRole, UserUpdate } from '../../../../shared' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { getFormattedObjects } from '../../../helpers/utils' | 5 | import { getFormattedObjects } from '../../../helpers/utils' |
6 | import { CONFIG, RATES_LIMIT, sequelizeTypescript } from '../../../initializers' | 6 | import { RATES_LIMIT, WEBSERVER } from '../../../initializers/constants' |
7 | import { Emailer } from '../../../lib/emailer' | 7 | import { Emailer } from '../../../lib/emailer' |
8 | import { Redis } from '../../../lib/redis' | 8 | import { Redis } from '../../../lib/redis' |
9 | import { createUserAccountAndChannel } from '../../../lib/user' | 9 | import { createUserAccountAndChannelAndPlaylist } from '../../../lib/user' |
10 | import { | 10 | import { |
11 | asyncMiddleware, | 11 | asyncMiddleware, |
12 | asyncRetryTransactionMiddleware, | 12 | asyncRetryTransactionMiddleware, |
@@ -38,23 +38,25 @@ import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../h | |||
38 | import { meRouter } from './me' | 38 | import { meRouter } from './me' |
39 | import { deleteUserToken } from '../../../lib/oauth-model' | 39 | import { deleteUserToken } from '../../../lib/oauth-model' |
40 | import { myBlocklistRouter } from './my-blocklist' | 40 | import { myBlocklistRouter } from './my-blocklist' |
41 | import { myVideoPlaylistsRouter } from './my-video-playlists' | ||
41 | import { myVideosHistoryRouter } from './my-history' | 42 | import { myVideosHistoryRouter } from './my-history' |
42 | import { myNotificationsRouter } from './my-notifications' | 43 | import { myNotificationsRouter } from './my-notifications' |
43 | import { Notifier } from '../../../lib/notifier' | 44 | import { Notifier } from '../../../lib/notifier' |
44 | import { mySubscriptionsRouter } from './my-subscriptions' | 45 | import { mySubscriptionsRouter } from './my-subscriptions' |
46 | import { CONFIG } from '../../../initializers/config' | ||
47 | import { sequelizeTypescript } from '../../../initializers/database' | ||
48 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | ||
45 | 49 | ||
46 | const auditLogger = auditLoggerFactory('users') | 50 | const auditLogger = auditLoggerFactory('users') |
47 | 51 | ||
48 | const loginRateLimiter = new RateLimit({ | 52 | const 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 | ||
54 | const askSendEmailLimiter = new RateLimit({ | 57 | const 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 | ||
60 | const usersRouter = express.Router() | 62 | const usersRouter = express.Router() |
@@ -62,6 +64,7 @@ usersRouter.use('/', myNotificationsRouter) | |||
62 | usersRouter.use('/', mySubscriptionsRouter) | 64 | usersRouter.use('/', mySubscriptionsRouter) |
63 | usersRouter.use('/', myBlocklistRouter) | 65 | usersRouter.use('/', myBlocklistRouter) |
64 | usersRouter.use('/', myVideosHistoryRouter) | 66 | usersRouter.use('/', myVideosHistoryRouter) |
67 | usersRouter.use('/', myVideoPlaylistsRouter) | ||
65 | usersRouter.use('/', meRouter) | 68 | usersRouter.use('/', meRouter) |
66 | 69 | ||
67 | usersRouter.get('/autocomplete', | 70 | usersRouter.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 | ||
224 | async function unblockUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 228 | async 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 | ||
232 | async function blockUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 236 | async 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 | ||
241 | function getUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 245 | function 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 | ||
245 | async function autocompleteUsers (req: express.Request, res: express.Response, next: express.NextFunction) { | 249 | async 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 | ||
251 | async function listUsers (req: express.Request, res: express.Response, next: express.NextFunction) { | 255 | async 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 | ||
257 | async function removeUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 261 | async 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 | ||
267 | async function updateUser (req: express.Request, res: express.Response, next: express.NextFunction) { | 271 | async 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 | ||
291 | async function askResetUserPassword (req: express.Request, res: express.Response, next: express.NextFunction) { | 297 | async 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 | ||
301 | async function resetUserPassword (req: express.Request, res: express.Response, next: express.NextFunction) { | 307 | async 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 | ||
310 | async function sendVerifyUserEmail (user: UserModel) { | 316 | async 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 | ||
317 | async function askSendVerifyUserEmail (req: express.Request, res: express.Response, next: express.NextFunction) { | 323 | async 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 | ||
325 | async function verifyUserEmail (req: express.Request, res: express.Response, next: express.NextFunction) { | 331 | async 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 | ||
334 | function success (req: express.Request, res: express.Response, next: express.NextFunction) { | 340 | function 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' | |||
2 | import 'multer' | 2 | import 'multer' |
3 | import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' | 3 | import { UserUpdateMe, UserVideoRate as FormattedUserVideoRate } from '../../../../shared' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers' | 5 | import { MIMETYPES } from '../../../initializers/constants' |
6 | import { sendUpdateActor } from '../../../lib/activitypub/send' | 6 | import { sendUpdateActor } from '../../../lib/activitypub/send' |
7 | import { | 7 | import { |
8 | asyncMiddleware, | 8 | asyncMiddleware, |
@@ -26,6 +26,8 @@ import { updateActorAvatarFile } from '../../../lib/avatar' | |||
26 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' | 26 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' |
27 | import { VideoImportModel } from '../../../models/video/video-import' | 27 | import { VideoImportModel } from '../../../models/video/video-import' |
28 | import { AccountModel } from '../../../models/account/account' | 28 | import { AccountModel } from '../../../models/account/account' |
29 | import { CONFIG } from '../../../initializers/config' | ||
30 | import { sequelizeTypescript } from '../../../initializers/database' | ||
29 | 31 | ||
30 | const auditLogger = auditLoggerFactory('users-me') | 32 | const auditLogger = auditLoggerFactory('users-me') |
31 | 33 | ||
@@ -93,8 +95,8 @@ export { | |||
93 | 95 | ||
94 | // --------------------------------------------------------------------------- | 96 | // --------------------------------------------------------------------------- |
95 | 97 | ||
96 | async function getUserVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 98 | async 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 | ||
114 | async function getUserVideoImports (req: express.Request, res: express.Response, next: express.NextFunction) { | 116 | async 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 | ||
126 | async function getUserInformation (req: express.Request, res: express.Response, next: express.NextFunction) { | 128 | async 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 | ||
133 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response, next: express.NextFunction) { | 135 | async 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 | ||
146 | async function getUserVideoRating (req: express.Request, res: express.Response, next: express.NextFunction) { | 148 | async 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 | ||
160 | async function deleteMe (req: express.Request, res: express.Response) { | 162 | async 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 | ||
170 | async function updateMe (req: express.Request, res: express.Response, next: express.NextFunction) { | 172 | async 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 | ||
200 | async function updateMyAvatar (req: express.Request, res: express.Response) { | 202 | async 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' |
20 | import { UserModel } from '../../../models/account/user' | ||
21 | import { AccountModel } from '../../../models/account/account' | ||
22 | import { AccountBlocklistModel } from '../../../models/account/account-blocklist' | 20 | import { AccountBlocklistModel } from '../../../models/account/account-blocklist' |
23 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' | 21 | import { addAccountInBlocklist, addServerInBlocklist, removeAccountFromBlocklist, removeServerFromBlocklist } from '../../../lib/blocklist' |
24 | import { ServerBlocklistModel } from '../../../models/server/server-blocklist' | 22 | import { ServerBlocklistModel } from '../../../models/server/server-blocklist' |
25 | import { ServerModel } from '../../../models/server/server' | ||
26 | 23 | ||
27 | const myBlocklistRouter = express.Router() | 24 | const myBlocklistRouter = express.Router() |
28 | 25 | ||
@@ -75,7 +72,7 @@ export { | |||
75 | // --------------------------------------------------------------------------- | 72 | // --------------------------------------------------------------------------- |
76 | 73 | ||
77 | async function listBlockedAccounts (req: express.Request, res: express.Response) { | 74 | async 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 | ||
85 | async function blockAccount (req: express.Request, res: express.Response) { | 82 | async 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 | ||
94 | async function unblockAccount (req: express.Request, res: express.Response) { | 91 | async 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 | ||
102 | async function listBlockedServers (req: express.Request, res: express.Response) { | 99 | async 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 | ||
110 | async function blockServer (req: express.Request, res: express.Response) { | 107 | async 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 | ||
119 | async function unblockServer (req: express.Request, res: express.Response) { | 116 | async 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 | ||
38 | async function listMyVideosHistory (req: express.Request, res: express.Response) { | 38 | async 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 | ||
46 | async function removeUserHistory (req: express.Request, res: express.Response) { | 46 | async 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' |
12 | import { UserModel } from '../../../models/account/user' | ||
13 | import { getFormattedObjects } from '../../../helpers/utils' | 12 | import { getFormattedObjects } from '../../../helpers/utils' |
14 | import { UserNotificationModel } from '../../../models/account/user-notification' | 13 | import { UserNotificationModel } from '../../../models/account/user-notification' |
15 | import { meRouter } from './me' | 14 | import { meRouter } from './me' |
@@ -57,8 +56,8 @@ export { | |||
57 | // --------------------------------------------------------------------------- | 56 | // --------------------------------------------------------------------------- |
58 | 57 | ||
59 | async function updateNotificationSettings (req: express.Request, res: express.Response) { | 58 | async 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 | ||
86 | async function listUserNotifications (req: express.Request, res: express.Response) { | 87 | async 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 | ||
94 | async function markAsReadUserNotifications (req: express.Request, res: express.Response) { | 95 | async 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 | ||
102 | async function markAsReadAllUserNotifications (req: express.Request, res: express.Response) { | 103 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'multer' | 2 | import 'multer' |
3 | import { getFormattedObjects } from '../../../helpers/utils' | 3 | import { getFormattedObjects } from '../../../helpers/utils' |
4 | import { CONFIG, sequelizeTypescript } from '../../../initializers' | 4 | import { WEBSERVER } from '../../../initializers/constants' |
5 | import { | 5 | import { |
6 | asyncMiddleware, | 6 | asyncMiddleware, |
7 | asyncRetryTransactionMiddleware, | 7 | asyncRetryTransactionMiddleware, |
@@ -14,13 +14,13 @@ import { | |||
14 | userSubscriptionGetValidator | 14 | userSubscriptionGetValidator |
15 | } from '../../../middlewares' | 15 | } from '../../../middlewares' |
16 | import { areSubscriptionsExistValidator, userSubscriptionsSortValidator, videosSortValidator } from '../../../middlewares/validators' | 16 | import { areSubscriptionsExistValidator, userSubscriptionsSortValidator, videosSortValidator } from '../../../middlewares/validators' |
17 | import { UserModel } from '../../../models/account/user' | ||
18 | import { VideoModel } from '../../../models/video/video' | 17 | import { VideoModel } from '../../../models/video/video' |
19 | import { buildNSFWFilter } from '../../../helpers/express-utils' | 18 | import { buildNSFWFilter } from '../../../helpers/express-utils' |
20 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' | 19 | import { VideoFilter } from '../../../../shared/models/videos/video-query.type' |
21 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 20 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
22 | import { JobQueue } from '../../../lib/job-queue' | 21 | import { JobQueue } from '../../../lib/job-queue' |
23 | import { logger } from '../../../helpers/logger' | 22 | import { logger } from '../../../helpers/logger' |
23 | import { sequelizeTypescript } from '../../../initializers/database' | ||
24 | 24 | ||
25 | const mySubscriptionsRouter = express.Router() | 25 | const mySubscriptionsRouter = express.Router() |
26 | 26 | ||
@@ -77,11 +77,11 @@ export { | |||
77 | 77 | ||
78 | async function areSubscriptionsExist (req: express.Request, res: express.Response) { | 78 | async 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 | ||
109 | async function addUserSubscription (req: express.Request, res: express.Response) { | 109 | async 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 | ||
125 | function getUserSubscription (req: express.Request, res: express.Response) { | 125 | function 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 | ||
131 | async function deleteUserSubscription (req: express.Request, res: express.Response) { | 131 | async 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 | ||
141 | async function getUserSubscriptions (req: express.Request, res: express.Response) { | 141 | async 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 | ||
150 | async function getUserSubscriptionVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 150 | async 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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { asyncMiddleware, authenticate } from '../../../middlewares' | ||
3 | import { doVideosInPlaylistExistValidator } from '../../../middlewares/validators/videos/video-playlists' | ||
4 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
5 | import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' | ||
6 | |||
7 | const myVideoPlaylistsRouter = express.Router() | ||
8 | |||
9 | myVideoPlaylistsRouter.get('/me/video-playlists/videos-exist', | ||
10 | authenticate, | ||
11 | doVideosInPlaylistExistValidator, | ||
12 | asyncMiddleware(doVideosInPlaylistExist) | ||
13 | ) | ||
14 | |||
15 | // --------------------------------------------------------------------------- | ||
16 | |||
17 | export { | ||
18 | myVideoPlaylistsRouter | ||
19 | } | ||
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | async 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' |
17 | import { VideoChannelModel } from '../../models/video/video-channel' | 18 | import { VideoChannelModel } from '../../models/video/video-channel' |
18 | import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' | 19 | import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' |
@@ -22,15 +23,18 @@ import { createVideoChannel } from '../../lib/video-channel' | |||
22 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 23 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
23 | import { setAsyncActorKeys } from '../../lib/activitypub' | 24 | import { setAsyncActorKeys } from '../../lib/activitypub' |
24 | import { AccountModel } from '../../models/account/account' | 25 | import { AccountModel } from '../../models/account/account' |
25 | import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers' | 26 | import { MIMETYPES } from '../../initializers/constants' |
26 | import { logger } from '../../helpers/logger' | 27 | import { logger } from '../../helpers/logger' |
27 | import { VideoModel } from '../../models/video/video' | 28 | import { VideoModel } from '../../models/video/video' |
28 | import { updateAvatarValidator } from '../../middlewares/validators/avatar' | 29 | import { updateAvatarValidator } from '../../middlewares/validators/avatar' |
29 | import { updateActorAvatarFile } from '../../lib/avatar' | 30 | import { updateActorAvatarFile } from '../../lib/avatar' |
30 | import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' | 31 | import { auditLoggerFactory, getAuditIdFromRes, VideoChannelAuditView } from '../../helpers/audit-logger' |
31 | import { resetSequelizeInstance } from '../../helpers/database-utils' | 32 | import { resetSequelizeInstance } from '../../helpers/database-utils' |
32 | import { UserModel } from '../../models/account/user' | ||
33 | import { JobQueue } from '../../lib/job-queue' | 33 | import { JobQueue } from '../../lib/job-queue' |
34 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
35 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' | ||
36 | import { CONFIG } from '../../initializers/config' | ||
37 | import { sequelizeTypescript } from '../../initializers/database' | ||
34 | 38 | ||
35 | const auditLogger = auditLoggerFactory('channels') | 39 | const auditLogger = auditLoggerFactory('channels') |
36 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 40 | const 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 | ||
84 | videoChannelRouter.get('/:nameWithHost/video-playlists', | ||
85 | asyncMiddleware(videoChannelsNameWithHostValidator), | ||
86 | paginationValidator, | ||
87 | videoPlaylistsSortValidator, | ||
88 | setDefaultSort, | ||
89 | setDefaultPagination, | ||
90 | commonVideoPlaylistFiltersValidator, | ||
91 | asyncMiddleware(listVideoChannelPlaylists) | ||
92 | ) | ||
93 | |||
80 | videoChannelRouter.get('/:nameWithHost/videos', | 94 | videoChannelRouter.get('/:nameWithHost/videos', |
81 | asyncMiddleware(videoChannelsNameWithHostValidator), | 95 | asyncMiddleware(videoChannelsNameWithHostValidator), |
82 | paginationValidator, | 96 | paginationValidator, |
@@ -96,16 +110,16 @@ export { | |||
96 | 110 | ||
97 | // --------------------------------------------------------------------------- | 111 | // --------------------------------------------------------------------------- |
98 | 112 | ||
99 | async function listVideoChannels (req: express.Request, res: express.Response, next: express.NextFunction) { | 113 | async 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 | ||
106 | async function updateVideoChannelAvatar (req: express.Request, res: express.Response, next: express.NextFunction) { | 120 | async 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 | ||
145 | async function updateVideoChannel (req: express.Request, res: express.Response) { | 159 | async 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 | ||
185 | async function removeVideoChannel (req: express.Request, res: express.Response) { | 199 | async 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 | ||
198 | async function getVideoChannel (req: express.Request, res: express.Response, next: express.NextFunction) { | 214 | async 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 | ||
209 | async function listVideoChannelVideos (req: express.Request, res: express.Response, next: express.NextFunction) { | 225 | async 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 | |||
240 | async 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 @@ | |||
1 | import * as express from 'express' | ||
2 | import { getFormattedObjects, getServerActor } from '../../helpers/utils' | ||
3 | import { | ||
4 | asyncMiddleware, | ||
5 | asyncRetryTransactionMiddleware, | ||
6 | authenticate, | ||
7 | commonVideosFiltersValidator, | ||
8 | optionalAuthenticate, | ||
9 | paginationValidator, | ||
10 | setDefaultPagination, | ||
11 | setDefaultSort | ||
12 | } from '../../middlewares' | ||
13 | import { videoPlaylistsSortValidator } from '../../middlewares/validators' | ||
14 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | ||
15 | import { MIMETYPES, VIDEO_PLAYLIST_PRIVACIES } from '../../initializers/constants' | ||
16 | import { logger } from '../../helpers/logger' | ||
17 | import { resetSequelizeInstance } from '../../helpers/database-utils' | ||
18 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
19 | import { | ||
20 | commonVideoPlaylistFiltersValidator, | ||
21 | videoPlaylistsAddValidator, | ||
22 | videoPlaylistsAddVideoValidator, | ||
23 | videoPlaylistsDeleteValidator, | ||
24 | videoPlaylistsGetValidator, | ||
25 | videoPlaylistsReorderVideosValidator, | ||
26 | videoPlaylistsUpdateOrRemoveVideoValidator, | ||
27 | videoPlaylistsUpdateValidator | ||
28 | } from '../../middlewares/validators/videos/video-playlists' | ||
29 | import { VideoPlaylistCreate } from '../../../shared/models/videos/playlist/video-playlist-create.model' | ||
30 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
31 | import { join } from 'path' | ||
32 | import { sendCreateVideoPlaylist, sendDeleteVideoPlaylist, sendUpdateVideoPlaylist } from '../../lib/activitypub/send' | ||
33 | import { getVideoPlaylistActivityPubUrl, getVideoPlaylistElementActivityPubUrl } from '../../lib/activitypub/url' | ||
34 | import { VideoPlaylistUpdate } from '../../../shared/models/videos/playlist/video-playlist-update.model' | ||
35 | import { VideoModel } from '../../models/video/video' | ||
36 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | ||
37 | import { VideoPlaylistElementCreate } from '../../../shared/models/videos/playlist/video-playlist-element-create.model' | ||
38 | import { VideoPlaylistElementUpdate } from '../../../shared/models/videos/playlist/video-playlist-element-update.model' | ||
39 | import { AccountModel } from '../../models/account/account' | ||
40 | import { VideoPlaylistReorder } from '../../../shared/models/videos/playlist/video-playlist-reorder.model' | ||
41 | import { JobQueue } from '../../lib/job-queue' | ||
42 | import { CONFIG } from '../../initializers/config' | ||
43 | import { sequelizeTypescript } from '../../initializers/database' | ||
44 | import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' | ||
45 | |||
46 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) | ||
47 | |||
48 | const videoPlaylistRouter = express.Router() | ||
49 | |||
50 | videoPlaylistRouter.get('/privacies', listVideoPlaylistPrivacies) | ||
51 | |||
52 | videoPlaylistRouter.get('/', | ||
53 | paginationValidator, | ||
54 | videoPlaylistsSortValidator, | ||
55 | setDefaultSort, | ||
56 | setDefaultPagination, | ||
57 | commonVideoPlaylistFiltersValidator, | ||
58 | asyncMiddleware(listVideoPlaylists) | ||
59 | ) | ||
60 | |||
61 | videoPlaylistRouter.get('/:playlistId', | ||
62 | asyncMiddleware(videoPlaylistsGetValidator), | ||
63 | getVideoPlaylist | ||
64 | ) | ||
65 | |||
66 | videoPlaylistRouter.post('/', | ||
67 | authenticate, | ||
68 | reqThumbnailFile, | ||
69 | asyncMiddleware(videoPlaylistsAddValidator), | ||
70 | asyncRetryTransactionMiddleware(addVideoPlaylist) | ||
71 | ) | ||
72 | |||
73 | videoPlaylistRouter.put('/:playlistId', | ||
74 | authenticate, | ||
75 | reqThumbnailFile, | ||
76 | asyncMiddleware(videoPlaylistsUpdateValidator), | ||
77 | asyncRetryTransactionMiddleware(updateVideoPlaylist) | ||
78 | ) | ||
79 | |||
80 | videoPlaylistRouter.delete('/:playlistId', | ||
81 | authenticate, | ||
82 | asyncMiddleware(videoPlaylistsDeleteValidator), | ||
83 | asyncRetryTransactionMiddleware(removeVideoPlaylist) | ||
84 | ) | ||
85 | |||
86 | videoPlaylistRouter.get('/:playlistId/videos', | ||
87 | asyncMiddleware(videoPlaylistsGetValidator), | ||
88 | paginationValidator, | ||
89 | setDefaultPagination, | ||
90 | optionalAuthenticate, | ||
91 | commonVideosFiltersValidator, | ||
92 | asyncMiddleware(getVideoPlaylistVideos) | ||
93 | ) | ||
94 | |||
95 | videoPlaylistRouter.post('/:playlistId/videos', | ||
96 | authenticate, | ||
97 | asyncMiddleware(videoPlaylistsAddVideoValidator), | ||
98 | asyncRetryTransactionMiddleware(addVideoInPlaylist) | ||
99 | ) | ||
100 | |||
101 | videoPlaylistRouter.post('/:playlistId/videos/reorder', | ||
102 | authenticate, | ||
103 | asyncMiddleware(videoPlaylistsReorderVideosValidator), | ||
104 | asyncRetryTransactionMiddleware(reorderVideosPlaylist) | ||
105 | ) | ||
106 | |||
107 | videoPlaylistRouter.put('/:playlistId/videos/:videoId', | ||
108 | authenticate, | ||
109 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), | ||
110 | asyncRetryTransactionMiddleware(updateVideoPlaylistElement) | ||
111 | ) | ||
112 | |||
113 | videoPlaylistRouter.delete('/:playlistId/videos/:videoId', | ||
114 | authenticate, | ||
115 | asyncMiddleware(videoPlaylistsUpdateOrRemoveVideoValidator), | ||
116 | asyncRetryTransactionMiddleware(removeVideoFromPlaylist) | ||
117 | ) | ||
118 | |||
119 | // --------------------------------------------------------------------------- | ||
120 | |||
121 | export { | ||
122 | videoPlaylistRouter | ||
123 | } | ||
124 | |||
125 | // --------------------------------------------------------------------------- | ||
126 | |||
127 | function listVideoPlaylistPrivacies (req: express.Request, res: express.Response) { | ||
128 | res.json(VIDEO_PLAYLIST_PRIVACIES) | ||
129 | } | ||
130 | |||
131 | async 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 | |||
144 | function 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 | |||
155 | async 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 | |||
202 | async 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 | |||
267 | async 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 | |||
281 | async 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 | |||
327 | async 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 | |||
351 | async 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 | |||
373 | async 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 | |||
421 | async 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 | |||
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers' |
6 | import { sendVideoAbuse } from '../../../lib/activitypub/send' | ||
7 | import { | 6 | import { |
8 | asyncMiddleware, | 7 | asyncMiddleware, |
9 | asyncRetryTransactionMiddleware, | 8 | asyncRetryTransactionMiddleware, |
@@ -18,11 +17,10 @@ import { | |||
18 | videoAbuseUpdateValidator | 17 | videoAbuseUpdateValidator |
19 | } from '../../../middlewares' | 18 | } from '../../../middlewares' |
20 | import { AccountModel } from '../../../models/account/account' | 19 | import { AccountModel } from '../../../models/account/account' |
21 | import { VideoModel } from '../../../models/video/video' | ||
22 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | 20 | import { VideoAbuseModel } from '../../../models/video/video-abuse' |
23 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' | 21 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' |
24 | import { UserModel } from '../../../models/account/user' | ||
25 | import { Notifier } from '../../../lib/notifier' | 22 | import { Notifier } from '../../../lib/notifier' |
23 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' | ||
26 | 24 | ||
27 | const auditLogger = auditLoggerFactory('abuse') | 25 | const auditLogger = auditLoggerFactory('abuse') |
28 | const abuseVideoRouter = express.Router() | 26 | const abuseVideoRouter = express.Router() |
@@ -69,7 +67,7 @@ async function listVideoAbuses (req: express.Request, res: express.Response) { | |||
69 | } | 67 | } |
70 | 68 | ||
71 | async function updateVideoAbuse (req: express.Request, res: express.Response) { | 69 | async 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 | ||
86 | async function deleteVideoAbuse (req: express.Request, res: express.Response) { | 84 | async 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 | ||
98 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 96 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { VideoBlacklist, UserRight, VideoBlacklistCreate } from '../../../../shared' | 2 | import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { | 5 | import { |
@@ -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' |
17 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | 18 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' |
18 | import { sequelizeTypescript } from '../../../initializers' | 19 | import { sequelizeTypescript } from '../../../initializers' |
19 | import { Notifier } from '../../../lib/notifier' | 20 | import { Notifier } from '../../../lib/notifier' |
20 | import { VideoModel } from '../../../models/video/video' | 21 | import { sendDeleteVideo } from '../../../lib/activitypub/send' |
21 | import { sendCreateVideo, sendDeleteVideo, sendUpdateVideo } from '../../../lib/activitypub/send' | ||
22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' | 22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' |
23 | 23 | ||
24 | const blacklistRouter = express.Router() | 24 | const 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 | ||
89 | async function updateVideoBlacklistController (req: express.Request, res: express.Response) { | 91 | async 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 | ||
101 | async function listBlacklist (req: express.Request, res: express.Response, next: express.NextFunction) { | 103 | async 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 | ||
107 | async function removeVideoFromBlacklistController (req: express.Request, res: express.Response, next: express.NextFunction) { | 109 | async 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' | |||
2 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' | 2 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate } from '../../../middlewares' |
3 | import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' | 3 | import { addVideoCaptionValidator, deleteVideoCaptionValidator, listVideoCaptionsValidator } from '../../../middlewares/validators' |
4 | import { createReqFiles } from '../../../helpers/express-utils' | 4 | import { createReqFiles } from '../../../helpers/express-utils' |
5 | import { CONFIG, MIMETYPES, sequelizeTypescript } from '../../../initializers' | 5 | import { MIMETYPES } from '../../../initializers/constants' |
6 | import { getFormattedObjects } from '../../../helpers/utils' | 6 | import { getFormattedObjects } from '../../../helpers/utils' |
7 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 7 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
8 | import { VideoModel } from '../../../models/video/video' | ||
9 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
10 | import { federateVideoIfNeeded } from '../../../lib/activitypub' | 9 | import { federateVideoIfNeeded } from '../../../lib/activitypub' |
11 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | 10 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' |
11 | import { CONFIG } from '../../../initializers/config' | ||
12 | import { sequelizeTypescript } from '../../../initializers/database' | ||
12 | 13 | ||
13 | const reqVideoCaptionAdd = createReqFiles( | 14 | const reqVideoCaptionAdd = createReqFiles( |
14 | [ 'captionfile' ], | 15 | [ 'captionfile' ], |
@@ -52,7 +53,7 @@ async function listVideoCaptions (req: express.Request, res: express.Response) { | |||
52 | 53 | ||
53 | async function addVideoCaption (req: express.Request, res: express.Response) { | 54 | async 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 | ||
76 | async function deleteVideoCaption (req: express.Request, res: express.Response) { | 77 | async 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 | |||
8 | import { | 8 | import { |
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' |
24 | import { VideoModel } from '../../../models/video/video' | ||
25 | import { VideoCommentModel } from '../../../models/video/video-comment' | 25 | import { VideoCommentModel } from '../../../models/video/video-comment' |
26 | import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' | 26 | import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../../helpers/audit-logger' |
27 | import { AccountModel } from '../../../models/account/account' | 27 | import { AccountModel } from '../../../models/account/account' |
28 | import { UserModel } from '../../../models/account/user' | ||
29 | import { Notifier } from '../../../lib/notifier' | 28 | import { Notifier } from '../../../lib/notifier' |
30 | 29 | ||
31 | const auditLogger = auditLoggerFactory('comments') | 30 | const auditLogger = auditLoggerFactory('comments') |
@@ -70,9 +69,9 @@ export { | |||
70 | 69 | ||
71 | // --------------------------------------------------------------------------- | 70 | // --------------------------------------------------------------------------- |
72 | 71 | ||
73 | async function listVideoThreads (req: express.Request, res: express.Response, next: express.NextFunction) { | 72 | async 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 | ||
91 | async function listVideoThreadComments (req: express.Request, res: express.Response, next: express.NextFunction) { | 90 | async 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 | ||
151 | async function removeVideoComment (req: express.Request, res: express.Response) { | 150 | async 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' | |||
3 | import 'multer' | 3 | import 'multer' |
4 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' | 4 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' |
5 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | 5 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' |
6 | import { CONFIG, MIMETYPES, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE } from '../../../initializers' | 6 | import { MIMETYPES } from '../../../initializers/constants' |
7 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' | 7 | import { getYoutubeDLInfo, YoutubeDLInfo } from '../../../helpers/youtube-dl' |
8 | import { createReqFiles } from '../../../helpers/express-utils' | 8 | import { createReqFiles } from '../../../helpers/express-utils' |
9 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
@@ -13,15 +13,19 @@ import { getVideoActivityPubUrl } from '../../../lib/activitypub' | |||
13 | import { TagModel } from '../../../models/video/tag' | 13 | import { TagModel } from '../../../models/video/tag' |
14 | import { VideoImportModel } from '../../../models/video/video-import' | 14 | import { VideoImportModel } from '../../../models/video/video-import' |
15 | import { JobQueue } from '../../../lib/job-queue/job-queue' | 15 | import { JobQueue } from '../../../lib/job-queue/job-queue' |
16 | import { processImage } from '../../../helpers/image-utils' | ||
17 | import { join } from 'path' | 16 | import { join } from 'path' |
18 | import { isArray } from '../../../helpers/custom-validators/misc' | 17 | import { isArray } from '../../../helpers/custom-validators/misc' |
19 | import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model' | ||
20 | import { VideoChannelModel } from '../../../models/video/video-channel' | 18 | import { VideoChannelModel } from '../../../models/video/video-channel' |
21 | import * as Bluebird from 'bluebird' | 19 | import * as Bluebird from 'bluebird' |
22 | import * as parseTorrent from 'parse-torrent' | 20 | import * as parseTorrent from 'parse-torrent' |
23 | import { getSecureTorrentName } from '../../../helpers/utils' | 21 | import { getSecureTorrentName } from '../../../helpers/utils' |
24 | import { readFile, move } from 'fs-extra' | 22 | import { move, readFile } from 'fs-extra' |
23 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' | ||
24 | import { CONFIG } from '../../../initializers/config' | ||
25 | import { sequelizeTypescript } from '../../../initializers/database' | ||
26 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' | ||
27 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | ||
28 | import { ThumbnailModel } from '../../../models/video/thumbnail' | ||
25 | 29 | ||
26 | const auditLogger = auditLoggerFactory('video-imports') | 30 | const auditLogger = auditLoggerFactory('video-imports') |
27 | const videoImportsRouter = express.Router() | 31 | const 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 | ||
194 | async function processPreview (req: express.Request, video: VideoModel) { | 213 | async 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 | ||
206 | function insertIntoDB ( | 224 | function 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' | |||
2 | import { extname, join } from 'path' | 2 | import { extname, join } from 'path' |
3 | import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' | 3 | import { VideoCreate, VideoPrivacy, VideoState, VideoUpdate } from '../../../../shared' |
4 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 4 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
5 | import { processImage } from '../../../helpers/image-utils' | ||
6 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
7 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' | 6 | import { auditLoggerFactory, getAuditIdFromRes, VideoAuditView } from '../../../helpers/audit-logger' |
8 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' | 7 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' |
9 | import { | 8 | import { autoBlacklistVideoIfNeeded } from '../../../lib/video-blacklist' |
10 | CONFIG, | 9 | import { 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' | ||
20 | import { | 10 | import { |
21 | changeVideoChannelShare, | 11 | changeVideoChannelShare, |
22 | federateVideoIfNeeded, | 12 | federateVideoIfNeeded, |
23 | fetchRemoteVideoDescription, | 13 | fetchRemoteVideoDescription, |
24 | getVideoActivityPubUrl | 14 | getVideoActivityPubUrl |
25 | } from '../../../lib/activitypub' | 15 | } from '../../../lib/activitypub' |
26 | import { sendCreateView } from '../../../lib/activitypub/send' | ||
27 | import { JobQueue } from '../../../lib/job-queue' | 16 | import { JobQueue } from '../../../lib/job-queue' |
28 | import { Redis } from '../../../lib/redis' | 17 | import { Redis } from '../../../lib/redis' |
29 | import { | 18 | import { |
@@ -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' | |||
59 | import { move } from 'fs-extra' | 49 | import { move } from 'fs-extra' |
60 | import { watchingRouter } from './watching' | 50 | import { watchingRouter } from './watching' |
61 | import { Notifier } from '../../../lib/notifier' | 51 | import { Notifier } from '../../../lib/notifier' |
52 | import { sendView } from '../../../lib/activitypub/send/send-view' | ||
53 | import { CONFIG } from '../../../initializers/config' | ||
54 | import { sequelizeTypescript } from '../../../initializers/database' | ||
55 | import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../../lib/thumbnail' | ||
56 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | ||
62 | 57 | ||
63 | const auditLogger = auditLoggerFactory('videos') | 58 | const auditLogger = auditLoggerFactory('videos') |
64 | const videosRouter = express.Router() | 59 | const videosRouter = express.Router() |
@@ -123,9 +118,9 @@ videosRouter.get('/:id/description', | |||
123 | ) | 118 | ) |
124 | videosRouter.get('/:id', | 119 | videosRouter.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 | ) |
130 | videosRouter.post('/:id/views', | 125 | videosRouter.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 | ||
294 | async function updateVideo (req: express.Request, res: express.Response) { | 298 | async 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 | ||
398 | function getVideo (req: express.Request, res: express.Response) { | 408 | async 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 | ||
409 | async function viewVideo (req: express.Request, res: express.Response) { | 421 | async 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 | ||
463 | async function removeVideo (req: express.Request, res: express.Response) { | 475 | async 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' |
14 | import { AccountModel } from '../../../models/account/account' | ||
15 | import { VideoModel } from '../../../models/video/video' | ||
16 | import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' | 14 | import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' |
17 | import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos' | 15 | import { VideoChangeOwnershipStatus, VideoPrivacy, VideoState } from '../../../../shared/models/videos' |
18 | import { VideoChannelModel } from '../../../models/video/video-channel' | 16 | import { VideoChannelModel } from '../../../models/video/video-channel' |
19 | import { getFormattedObjects } from '../../../helpers/utils' | 17 | import { getFormattedObjects } from '../../../helpers/utils' |
20 | import { changeVideoChannelShare } from '../../../lib/activitypub' | 18 | import { changeVideoChannelShare } from '../../../lib/activitypub' |
21 | import { sendUpdateVideo } from '../../../lib/activitypub/send' | 19 | import { sendUpdateVideo } from '../../../lib/activitypub/send' |
22 | import { UserModel } from '../../../models/account/user' | 20 | import { VideoModel } from '../../../models/video/video' |
23 | 21 | ||
24 | const ownershipVideoRouter = express.Router() | 22 | const ownershipVideoRouter = express.Router() |
25 | 23 | ||
@@ -58,9 +56,9 @@ export { | |||
58 | // --------------------------------------------------------------------------- | 56 | // --------------------------------------------------------------------------- |
59 | 57 | ||
60 | async function giveVideoOwnership (req: express.Request, res: express.Response) { | 58 | async 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 | ||
87 | async function listVideoOwnership (req: express.Request, res: express.Response) { | 85 | async 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 | ||
100 | async function acceptOwnership (req: express.Request, res: express.Response) { | 98 | async 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 | ||
124 | async function refuseOwnership (req: express.Request, res: express.Response) { | 125 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserVideoRateUpdate } from '../../../../shared' | 2 | import { UserVideoRateUpdate } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { sequelizeTypescript, VIDEO_RATE_TYPES } from '../../../initializers' | 4 | import { VIDEO_RATE_TYPES } from '../../../initializers/constants' |
5 | import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub' | 5 | import { getRateUrl, sendVideoRateChange } from '../../../lib/activitypub' |
6 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares' | 6 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoUpdateRateValidator } from '../../../middlewares' |
7 | import { AccountModel } from '../../../models/account/account' | 7 | import { AccountModel } from '../../../models/account/account' |
8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
9 | import { VideoModel } from '../../../models/video/video' | 9 | import { sequelizeTypescript } from '../../../initializers/database' |
10 | 10 | ||
11 | const rateVideoRouter = express.Router() | 11 | const rateVideoRouter = express.Router() |
12 | 12 | ||
@@ -27,8 +27,8 @@ export { | |||
27 | async function rateVideo (req: express.Request, res: express.Response) { | 27 | async 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' | |||
2 | import { UserWatchingVideo } from '../../../../shared' | 2 | import { UserWatchingVideo } from '../../../../shared' |
3 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares' | 3 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoWatchingValidator } from '../../../middlewares' |
4 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | 4 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' |
5 | import { UserModel } from '../../../models/account/user' | ||
6 | 5 | ||
7 | const watchingRouter = express.Router() | 6 | const watchingRouter = express.Router() |
8 | 7 | ||
@@ -21,7 +20,7 @@ export { | |||
21 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
22 | 21 | ||
23 | async function userWatchVideo (req: express.Request, res: express.Response) { | 22 | async 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { asyncMiddleware } from '../middlewares' | 2 | import { asyncMiddleware } from '../middlewares' |
3 | import { CONFIG, ROUTE_CACHE_LIFETIME } from '../initializers' | 3 | import { ROUTE_CACHE_LIFETIME, WEBSERVER } from '../initializers/constants' |
4 | import * as sitemapModule from 'sitemap' | 4 | import * as sitemapModule from 'sitemap' |
5 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
6 | import { VideoModel } from '../models/video/video' | 6 | import { 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { root } from '../helpers/core-utils' | 3 | import { root } from '../helpers/core-utils' |
4 | import { ACCEPT_HEADERS, STATIC_MAX_AGE } from '../initializers' | 4 | import { ACCEPT_HEADERS, STATIC_MAX_AGE } from '../initializers/constants' |
5 | import { asyncMiddleware, embedCSP } from '../middlewares' | 5 | import { asyncMiddleware, embedCSP } from '../middlewares' |
6 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '../../shared/models/i18n/i18n' | 6 | import { buildFileLocale, getCompleteLocale, is18nLocale, LOCALE_FILES } from '../../shared/models/i18n/i18n' |
7 | import { ClientHtml } from '../lib/client-html' | 7 | import { 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 |
19 | clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage)) | 19 | clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage)) |
20 | clientsRouter.use('/accounts/:nameWithHost', asyncMiddleware(generateAccountHtmlPage)) | ||
21 | clientsRouter.use('/video-channels/:nameWithHost', asyncMiddleware(generateVideoChannelHtmlPage)) | ||
20 | 22 | ||
21 | clientsRouter.use( | 23 | clientsRouter.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 | ||
104 | async 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 | |||
110 | async 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 | |||
102 | function sendHTML (html: string, res: express.Response) { | 116 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { CONFIG, FEEDS, ROUTE_CACHE_LIFETIME } from '../initializers/constants' | 2 | import { FEEDS, ROUTE_CACHE_LIFETIME, THUMBNAILS_SIZE, WEBSERVER } from '../initializers/constants' |
3 | import { THUMBNAILS_SIZE } from '../initializers' | ||
4 | import { | 3 | import { |
5 | asyncMiddleware, | 4 | asyncMiddleware, |
6 | commonVideosFiltersValidator, | 5 | commonVideosFiltersValidator, |
@@ -11,11 +10,10 @@ import { | |||
11 | } from '../middlewares' | 10 | } from '../middlewares' |
12 | import { VideoModel } from '../models/video/video' | 11 | import { VideoModel } from '../models/video/video' |
13 | import * as Feed from 'pfeed' | 12 | import * as Feed from 'pfeed' |
14 | import { AccountModel } from '../models/account/account' | ||
15 | import { cacheRoute } from '../middlewares/cache' | 13 | import { cacheRoute } from '../middlewares/cache' |
16 | import { VideoChannelModel } from '../models/video/video-channel' | ||
17 | import { VideoCommentModel } from '../models/video/video-comment' | 14 | import { VideoCommentModel } from '../models/video/video-comment' |
18 | import { buildNSFWFilter } from '../helpers/express-utils' | 15 | import { buildNSFWFilter } from '../helpers/express-utils' |
16 | import { CONFIG } from '../initializers/config' | ||
19 | 17 | ||
20 | const feedsRouter = express.Router() | 18 | const feedsRouter = express.Router() |
21 | 19 | ||
@@ -42,10 +40,10 @@ export { | |||
42 | 40 | ||
43 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
44 | 42 | ||
45 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response, next: express.NextFunction) { | 43 | async 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 | ||
80 | async function generateVideoFeed (req: express.Request, res: express.Response, next: express.NextFunction) { | 78 | async 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 | ||
154 | function initFeed (name: string, description: string) { | 152 | function 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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { CONFIG, EMBED_SIZE, PREVIEWS_SIZE } from '../initializers' | 2 | import { EMBED_SIZE, PREVIEWS_SIZE, WEBSERVER } from '../initializers/constants' |
3 | import { asyncMiddleware, oembedValidator } from '../middlewares' | 3 | import { asyncMiddleware, oembedValidator } from '../middlewares' |
4 | import { accountsNameWithHostGetValidator } from '../middlewares/validators' | 4 | import { accountNameWithHostGetValidator } from '../middlewares/validators' |
5 | import { VideoModel } from '../models/video/video' | ||
6 | 5 | ||
7 | const servicesRouter = express.Router() | 6 | const servicesRouter = express.Router() |
8 | 7 | ||
@@ -11,7 +10,7 @@ servicesRouter.use('/oembed', | |||
11 | generateOEmbed | 10 | generateOEmbed |
12 | ) | 11 | ) |
13 | servicesRouter.use('/redirect/accounts/:accountName', | 12 | servicesRouter.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 | ||
26 | function generateOEmbed (req: express.Request, res: express.Response, next: express.NextFunction) { | 25 | function 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 @@ | |||
1 | import * as cors from 'cors' | 1 | import * as cors from 'cors' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { CONFIG, ROUTE_CACHE_LIFETIME, STATIC_DOWNLOAD_PATHS, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers' | 3 | import { |
4 | import { 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' | ||
11 | import { VideosCaptionCache, VideosPreviewCache } from '../lib/files-cache' | ||
5 | import { cacheRoute } from '../middlewares/cache' | 12 | import { cacheRoute } from '../middlewares/cache' |
6 | import { asyncMiddleware, videosGetValidator } from '../middlewares' | 13 | import { asyncMiddleware, videosGetValidator } from '../middlewares' |
7 | import { VideoModel } from '../models/video/video' | 14 | import { VideoModel } from '../models/video/video' |
8 | import { VideosCaptionCache } from '../lib/cache/videos-caption-cache' | ||
9 | import { UserModel } from '../models/account/user' | 15 | import { UserModel } from '../models/account/user' |
10 | import { VideoCommentModel } from '../models/video/video-comment' | 16 | import { VideoCommentModel } from '../models/video/video-comment' |
11 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' | 17 | import { HttpNodeinfoDiasporaSoftwareNsSchema20 } from '../../shared/models/nodeinfo' |
12 | import { join } from 'path' | 18 | import { join } from 'path' |
13 | import { root } from '../helpers/core-utils' | 19 | import { root } from '../helpers/core-utils' |
20 | import { CONFIG } from '../initializers/config' | ||
14 | 21 | ||
15 | const packageJSON = require('../../../package.json') | 22 | const packageJSON = require('../../../package.json') |
16 | const staticRouter = express.Router() | 23 | const staticRouter = express.Router() |
@@ -51,6 +58,13 @@ staticRouter.use( | |||
51 | asyncMiddleware(downloadVideoFile) | 58 | asyncMiddleware(downloadVideoFile) |
52 | ) | 59 | ) |
53 | 60 | ||
61 | // HLS | ||
62 | staticRouter.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 |
55 | const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR | 69 | const thumbnailsPhysicalPath = CONFIG.STORAGE.THUMBNAILS_DIR |
56 | staticRouter.use( | 70 | staticRouter.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 | ||
153 | async function getPreview (req: express.Request, res: express.Response, next: express.NextFunction) { | 167 | async 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 | ||
160 | async function getVideoCaption (req: express.Request, res: express.Response) { | 174 | async 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 | ||
170 | async function generateNodeinfo (req: express.Request, res: express.Response, next: express.NextFunction) { | 184 | async 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 | ||
232 | function getVideoAndFile (req: express.Request, res: express.Response) { | 246 | function 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' | |||
4 | import * as bitTorrentTracker from 'bittorrent-tracker' | 4 | import * as bitTorrentTracker from 'bittorrent-tracker' |
5 | import * as proxyAddr from 'proxy-addr' | 5 | import * as proxyAddr from 'proxy-addr' |
6 | import { Server as WebSocketServer } from 'ws' | 6 | import { Server as WebSocketServer } from 'ws' |
7 | import { CONFIG, TRACKER_RATE_LIMITS } from '../initializers/constants' | 7 | import { TRACKER_RATE_LIMITS } from '../initializers/constants' |
8 | import { VideoFileModel } from '../models/video/video-file' | 8 | import { VideoFileModel } from '../models/video/video-file' |
9 | import { parse } from 'url' | 9 | import { parse } from 'url' |
10 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
11 | import { CONFIG } from '../initializers/config' | ||
10 | 12 | ||
11 | const TrackerServer = bitTorrentTracker.Server | 13 | const 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 | ||
51 | trackerServer.on('error', function (err) { | 65 | if (CONFIG.TRACKER.ENABLED !== false) { |
52 | logger.error('Error in tracker.', { err }) | ||
53 | }) | ||
54 | 66 | ||
55 | trackerServer.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 | ||
59 | const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer) | 76 | const onHttpRequest = trackerServer.onHttpRequest.bind(trackerServer) |
60 | trackerRouter.get('/tracker/announce', (req, res) => onHttpRequest(req, res, { action: 'announce' })) | 77 | trackerRouter.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 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { asyncMiddleware } from '../middlewares' | 2 | import { asyncMiddleware } from '../middlewares' |
3 | import { webfingerValidator } from '../middlewares/validators' | 3 | import { webfingerValidator } from '../middlewares/validators' |
4 | import { ActorModel } from '../models/activitypub/actor' | ||
5 | 4 | ||
6 | const webfingerRouter = express.Router() | 5 | const webfingerRouter = express.Router() |
7 | 6 | ||
@@ -18,8 +17,8 @@ export { | |||
18 | 17 | ||
19 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
20 | 19 | ||
21 | function webfingerController (req: express.Request, res: express.Response, next: express.NextFunction) { | 20 | function 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, |