diff options
Diffstat (limited to 'server')
341 files changed, 13056 insertions, 4031 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, |
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index f1430055f..951a25669 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -2,7 +2,7 @@ import * as Bluebird from 'bluebird' | |||
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { ResultList } from '../../shared/models' | 3 | import { ResultList } from '../../shared/models' |
4 | import { Activity } from '../../shared/models/activitypub' | 4 | import { Activity } from '../../shared/models/activitypub' |
5 | import { ACTIVITY_PUB } from '../initializers' | 5 | import { ACTIVITY_PUB } from '../initializers/constants' |
6 | import { ActorModel } from '../models/activitypub/actor' | 6 | import { ActorModel } from '../models/activitypub/actor' |
7 | import { signJsonLDObject } from './peertube-crypto' | 7 | import { signJsonLDObject } from './peertube-crypto' |
8 | import { pageToStartAndCount } from './core-utils' | 8 | import { pageToStartAndCount } from './core-utils' |
@@ -15,7 +15,7 @@ function activityPubContextify <T> (data: T) { | |||
15 | 'https://w3id.org/security/v1', | 15 | 'https://w3id.org/security/v1', |
16 | { | 16 | { |
17 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', | 17 | RsaSignature2017: 'https://w3id.org/security#RsaSignature2017', |
18 | pt: 'https://joinpeertube.org/ns', | 18 | pt: 'https://joinpeertube.org/ns#', |
19 | sc: 'http://schema.org#', | 19 | sc: 'http://schema.org#', |
20 | Hashtag: 'as:Hashtag', | 20 | Hashtag: 'as:Hashtag', |
21 | uuid: 'sc:identifier', | 21 | uuid: 'sc:identifier', |
@@ -24,15 +24,54 @@ function activityPubContextify <T> (data: T) { | |||
24 | subtitleLanguage: 'sc:subtitleLanguage', | 24 | subtitleLanguage: 'sc:subtitleLanguage', |
25 | sensitive: 'as:sensitive', | 25 | sensitive: 'as:sensitive', |
26 | language: 'sc:inLanguage', | 26 | language: 'sc:inLanguage', |
27 | views: 'sc:Number', | ||
28 | state: 'sc:Number', | ||
29 | size: 'sc:Number', | ||
30 | fps: 'sc:Number', | ||
31 | commentsEnabled: 'sc:Boolean', | ||
32 | waitTranscoding: 'sc:Boolean', | ||
33 | expires: 'sc:expires', | 27 | expires: 'sc:expires', |
34 | support: 'sc:Text', | 28 | CacheFile: 'pt:CacheFile', |
35 | CacheFile: 'pt:CacheFile' | 29 | Infohash: 'pt:Infohash', |
30 | originallyPublishedAt: 'sc:datePublished', | ||
31 | views: { | ||
32 | '@type': 'sc:Number', | ||
33 | '@id': 'pt:views' | ||
34 | }, | ||
35 | state: { | ||
36 | '@type': 'sc:Number', | ||
37 | '@id': 'pt:state' | ||
38 | }, | ||
39 | size: { | ||
40 | '@type': 'sc:Number', | ||
41 | '@id': 'pt:size' | ||
42 | }, | ||
43 | fps: { | ||
44 | '@type': 'sc:Number', | ||
45 | '@id': 'pt:fps' | ||
46 | }, | ||
47 | startTimestamp: { | ||
48 | '@type': 'sc:Number', | ||
49 | '@id': 'pt:startTimestamp' | ||
50 | }, | ||
51 | stopTimestamp: { | ||
52 | '@type': 'sc:Number', | ||
53 | '@id': 'pt:stopTimestamp' | ||
54 | }, | ||
55 | position: { | ||
56 | '@type': 'sc:Number', | ||
57 | '@id': 'pt:position' | ||
58 | }, | ||
59 | commentsEnabled: { | ||
60 | '@type': 'sc:Boolean', | ||
61 | '@id': 'pt:commentsEnabled' | ||
62 | }, | ||
63 | downloadEnabled: { | ||
64 | '@type': 'sc:Boolean', | ||
65 | '@id': 'pt:downloadEnabled' | ||
66 | }, | ||
67 | waitTranscoding: { | ||
68 | '@type': 'sc:Boolean', | ||
69 | '@id': 'pt:waitTranscoding' | ||
70 | }, | ||
71 | support: { | ||
72 | '@type': 'sc:Text', | ||
73 | '@id': 'pt:support' | ||
74 | } | ||
36 | }, | 75 | }, |
37 | { | 76 | { |
38 | likes: { | 77 | likes: { |
@@ -43,6 +82,10 @@ function activityPubContextify <T> (data: T) { | |||
43 | '@id': 'as:dislikes', | 82 | '@id': 'as:dislikes', |
44 | '@type': '@id' | 83 | '@type': '@id' |
45 | }, | 84 | }, |
85 | playlists: { | ||
86 | '@id': 'pt:playlists', | ||
87 | '@type': '@id' | ||
88 | }, | ||
46 | shares: { | 89 | shares: { |
47 | '@id': 'as:shares', | 90 | '@id': 'as:shares', |
48 | '@type': '@id' | 91 | '@type': '@id' |
@@ -64,7 +107,7 @@ async function activityPubCollectionPagination (baseUrl: string, handler: Activi | |||
64 | 107 | ||
65 | return { | 108 | return { |
66 | id: baseUrl, | 109 | id: baseUrl, |
67 | type: 'OrderedCollection', | 110 | type: 'OrderedCollectionPage', |
68 | totalItems: result.total, | 111 | totalItems: result.total, |
69 | first: baseUrl + '?page=1' | 112 | first: baseUrl + '?page=1' |
70 | } | 113 | } |
diff --git a/server/helpers/audit-logger.ts b/server/helpers/audit-logger.ts index 00311fce1..f536da439 100644 --- a/server/helpers/audit-logger.ts +++ b/server/helpers/audit-logger.ts | |||
@@ -4,15 +4,14 @@ import { diff } from 'deep-object-diff' | |||
4 | import { chain } from 'lodash' | 4 | import { chain } from 'lodash' |
5 | import * as flatten from 'flat' | 5 | import * as flatten from 'flat' |
6 | import * as winston from 'winston' | 6 | import * as winston from 'winston' |
7 | import { CONFIG } from '../initializers' | ||
8 | import { jsonLoggerFormat, labelFormatter } from './logger' | 7 | import { jsonLoggerFormat, labelFormatter } from './logger' |
9 | import { VideoDetails, User, VideoChannel, VideoAbuse, VideoImport } from '../../shared' | 8 | import { User, VideoAbuse, VideoChannel, VideoDetails, VideoImport } from '../../shared' |
10 | import { VideoComment } from '../../shared/models/videos/video-comment.model' | 9 | import { VideoComment } from '../../shared/models/videos/video-comment.model' |
11 | import { CustomConfig } from '../../shared/models/server/custom-config.model' | 10 | import { CustomConfig } from '../../shared/models/server/custom-config.model' |
12 | import { UserModel } from '../models/account/user' | 11 | import { CONFIG } from '../initializers/config' |
13 | 12 | ||
14 | function getAuditIdFromRes (res: express.Response) { | 13 | function getAuditIdFromRes (res: express.Response) { |
15 | return (res.locals.oauth.token.User as UserModel).username | 14 | return res.locals.oauth.token.User.username |
16 | } | 15 | } |
17 | 16 | ||
18 | enum AUDIT_TYPE { | 17 | enum AUDIT_TYPE { |
@@ -117,7 +116,8 @@ const videoKeysToKeep = [ | |||
117 | 'channel-uuid', | 116 | 'channel-uuid', |
118 | 'channel-name', | 117 | 'channel-name', |
119 | 'support', | 118 | 'support', |
120 | 'commentsEnabled' | 119 | 'commentsEnabled', |
120 | 'downloadEnabled' | ||
121 | ] | 121 | ] |
122 | class VideoAuditView extends EntityAuditView { | 122 | class VideoAuditView extends EntityAuditView { |
123 | constructor (private video: VideoDetails) { | 123 | constructor (private video: VideoDetails) { |
diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 0fb11a125..7174d4654 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { CONFIG } from '../initializers' | 2 | import { CONFIG } from '../initializers/config' |
3 | import { VideoCaptionModel } from '../models/video/video-caption' | 3 | import { VideoCaptionModel } from '../models/video/video-caption' |
4 | import * as srt2vtt from 'srt-to-vtt' | 4 | import * as srt2vtt from 'srt-to-vtt' |
5 | import { createReadStream, createWriteStream, remove, move } from 'fs-extra' | 5 | import { createReadStream, createWriteStream, remove, move } from 'fs-extra' |
diff --git a/server/helpers/core-utils.ts b/server/helpers/core-utils.ts index 3fb824e36..305d3b71e 100644 --- a/server/helpers/core-utils.ts +++ b/server/helpers/core-utils.ts | |||
@@ -11,14 +11,13 @@ import * as pem from 'pem' | |||
11 | import { URL } from 'url' | 11 | import { URL } from 'url' |
12 | import { truncate } from 'lodash' | 12 | import { truncate } from 'lodash' |
13 | import { exec } from 'child_process' | 13 | import { exec } from 'child_process' |
14 | import { isArray } from './custom-validators/misc' | ||
15 | 14 | ||
16 | const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => { | 15 | const objectConverter = (oldObject: any, keyConverter: (e: string) => string, valueConverter: (e: any) => any) => { |
17 | if (!oldObject || typeof oldObject !== 'object') { | 16 | if (!oldObject || typeof oldObject !== 'object') { |
18 | return valueConverter(oldObject) | 17 | return valueConverter(oldObject) |
19 | } | 18 | } |
20 | 19 | ||
21 | if (isArray(oldObject)) { | 20 | if (Array.isArray(oldObject)) { |
22 | return oldObject.map(e => objectConverter(e, keyConverter, valueConverter)) | 21 | return oldObject.map(e => objectConverter(e, keyConverter, valueConverter)) |
23 | } | 22 | } |
24 | 23 | ||
@@ -41,7 +40,7 @@ const timeTable = { | |||
41 | month: 3600000 * 24 * 30 | 40 | month: 3600000 * 24 * 30 |
42 | } | 41 | } |
43 | 42 | ||
44 | export function parseDuration (duration: number | string): number { | 43 | export function parseDurationToMs (duration: number | string): number { |
45 | if (typeof duration === 'number') return duration | 44 | if (typeof duration === 'number') return duration |
46 | 45 | ||
47 | if (typeof duration === 'string') { | 46 | if (typeof duration === 'string') { |
@@ -58,7 +57,7 @@ export function parseDuration (duration: number | string): number { | |||
58 | } | 57 | } |
59 | } | 58 | } |
60 | 59 | ||
61 | throw new Error('Duration could not be properly parsed') | 60 | throw new Error(`Duration ${duration} could not be properly parsed`) |
62 | } | 61 | } |
63 | 62 | ||
64 | export function parseBytes (value: string | number): number { | 63 | export function parseBytes (value: string | number): number { |
@@ -193,10 +192,14 @@ function peertubeTruncate (str: string, maxLength: number) { | |||
193 | return truncate(str, options) | 192 | return truncate(str, options) |
194 | } | 193 | } |
195 | 194 | ||
196 | function sha256 (str: string, encoding: HexBase64Latin1Encoding = 'hex') { | 195 | function sha256 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') { |
197 | return createHash('sha256').update(str).digest(encoding) | 196 | return createHash('sha256').update(str).digest(encoding) |
198 | } | 197 | } |
199 | 198 | ||
199 | function sha1 (str: string | Buffer, encoding: HexBase64Latin1Encoding = 'hex') { | ||
200 | return createHash('sha1').update(str).digest(encoding) | ||
201 | } | ||
202 | |||
200 | function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> { | 203 | function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> { |
201 | return function promisified (): Promise<A> { | 204 | return function promisified (): Promise<A> { |
202 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { | 205 | return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => { |
@@ -262,7 +265,9 @@ export { | |||
262 | sanitizeHost, | 265 | sanitizeHost, |
263 | buildPath, | 266 | buildPath, |
264 | peertubeTruncate, | 267 | peertubeTruncate, |
268 | |||
265 | sha256, | 269 | sha256, |
270 | sha1, | ||
266 | 271 | ||
267 | promisify0, | 272 | promisify0, |
268 | promisify1, | 273 | promisify1, |
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index 27a187db1..a3bceb047 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts | |||
@@ -1,13 +1,77 @@ | |||
1 | import * as AsyncLRU from 'async-lru' | 1 | import * as AsyncLRU from 'async-lru' |
2 | import * as jsonld from 'jsonld' | 2 | import * as jsonld from 'jsonld' |
3 | import * as jsig from 'jsonld-signatures' | 3 | import * as jsig from 'jsonld-signatures' |
4 | import { logger } from './logger' | ||
5 | |||
6 | const CACHE = { | ||
7 | 'https://w3id.org/security/v1': { | ||
8 | '@context': { | ||
9 | 'id': '@id', | ||
10 | 'type': '@type', | ||
11 | |||
12 | 'dc': 'http://purl.org/dc/terms/', | ||
13 | 'sec': 'https://w3id.org/security#', | ||
14 | 'xsd': 'http://www.w3.org/2001/XMLSchema#', | ||
15 | |||
16 | 'EcdsaKoblitzSignature2016': 'sec:EcdsaKoblitzSignature2016', | ||
17 | 'Ed25519Signature2018': 'sec:Ed25519Signature2018', | ||
18 | 'EncryptedMessage': 'sec:EncryptedMessage', | ||
19 | 'GraphSignature2012': 'sec:GraphSignature2012', | ||
20 | 'LinkedDataSignature2015': 'sec:LinkedDataSignature2015', | ||
21 | 'LinkedDataSignature2016': 'sec:LinkedDataSignature2016', | ||
22 | 'CryptographicKey': 'sec:Key', | ||
23 | |||
24 | 'authenticationTag': 'sec:authenticationTag', | ||
25 | 'canonicalizationAlgorithm': 'sec:canonicalizationAlgorithm', | ||
26 | 'cipherAlgorithm': 'sec:cipherAlgorithm', | ||
27 | 'cipherData': 'sec:cipherData', | ||
28 | 'cipherKey': 'sec:cipherKey', | ||
29 | 'created': { '@id': 'dc:created', '@type': 'xsd:dateTime' }, | ||
30 | 'creator': { '@id': 'dc:creator', '@type': '@id' }, | ||
31 | 'digestAlgorithm': 'sec:digestAlgorithm', | ||
32 | 'digestValue': 'sec:digestValue', | ||
33 | 'domain': 'sec:domain', | ||
34 | 'encryptionKey': 'sec:encryptionKey', | ||
35 | 'expiration': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, | ||
36 | 'expires': { '@id': 'sec:expiration', '@type': 'xsd:dateTime' }, | ||
37 | 'initializationVector': 'sec:initializationVector', | ||
38 | 'iterationCount': 'sec:iterationCount', | ||
39 | 'nonce': 'sec:nonce', | ||
40 | 'normalizationAlgorithm': 'sec:normalizationAlgorithm', | ||
41 | 'owner': { '@id': 'sec:owner', '@type': '@id' }, | ||
42 | 'password': 'sec:password', | ||
43 | 'privateKey': { '@id': 'sec:privateKey', '@type': '@id' }, | ||
44 | 'privateKeyPem': 'sec:privateKeyPem', | ||
45 | 'publicKey': { '@id': 'sec:publicKey', '@type': '@id' }, | ||
46 | 'publicKeyBase58': 'sec:publicKeyBase58', | ||
47 | 'publicKeyPem': 'sec:publicKeyPem', | ||
48 | 'publicKeyWif': 'sec:publicKeyWif', | ||
49 | 'publicKeyService': { '@id': 'sec:publicKeyService', '@type': '@id' }, | ||
50 | 'revoked': { '@id': 'sec:revoked', '@type': 'xsd:dateTime' }, | ||
51 | 'salt': 'sec:salt', | ||
52 | 'signature': 'sec:signature', | ||
53 | 'signatureAlgorithm': 'sec:signingAlgorithm', | ||
54 | 'signatureValue': 'sec:signatureValue' | ||
55 | } | ||
56 | } | ||
57 | } | ||
4 | 58 | ||
5 | const nodeDocumentLoader = jsonld.documentLoaders.node() | 59 | const nodeDocumentLoader = jsonld.documentLoaders.node() |
6 | 60 | ||
7 | const lru = new AsyncLRU({ | 61 | const lru = new AsyncLRU({ |
8 | max: 10, | 62 | max: 10, |
9 | load: (key, cb) => { | 63 | load: (url, cb) => { |
10 | nodeDocumentLoader(key, cb) | 64 | if (CACHE[ url ] !== undefined) { |
65 | logger.debug('Using cache for JSON-LD %s.', url) | ||
66 | |||
67 | return cb(null, { | ||
68 | contextUrl: null, | ||
69 | document: CACHE[ url ], | ||
70 | documentUrl: url | ||
71 | }) | ||
72 | } | ||
73 | |||
74 | nodeDocumentLoader(url, cb) | ||
11 | } | 75 | } |
12 | }) | 76 | }) |
13 | 77 | ||
diff --git a/server/helpers/custom-validators/accounts.ts b/server/helpers/custom-validators/accounts.ts index 191de1496..146c7708e 100644 --- a/server/helpers/custom-validators/accounts.ts +++ b/server/helpers/custom-validators/accounts.ts | |||
@@ -5,7 +5,6 @@ import * as validator from 'validator' | |||
5 | import { AccountModel } from '../../models/account/account' | 5 | import { AccountModel } from '../../models/account/account' |
6 | import { isUserDescriptionValid, isUserUsernameValid } from './users' | 6 | import { isUserDescriptionValid, isUserUsernameValid } from './users' |
7 | import { exists } from './misc' | 7 | import { exists } from './misc' |
8 | import { CONFIG } from '../../initializers' | ||
9 | 8 | ||
10 | function isAccountNameValid (value: string) { | 9 | function isAccountNameValid (value: string) { |
11 | return isUserUsernameValid(value) | 10 | return isUserUsernameValid(value) |
@@ -19,7 +18,7 @@ function isAccountDescriptionValid (value: string) { | |||
19 | return isUserDescriptionValid(value) | 18 | return isUserDescriptionValid(value) |
20 | } | 19 | } |
21 | 20 | ||
22 | function isAccountIdExist (id: number | string, res: Response, sendNotFound = true) { | 21 | function doesAccountIdExist (id: number | string, res: Response, sendNotFound = true) { |
23 | let promise: Bluebird<AccountModel> | 22 | let promise: Bluebird<AccountModel> |
24 | 23 | ||
25 | if (validator.isInt('' + id)) { | 24 | if (validator.isInt('' + id)) { |
@@ -28,26 +27,20 @@ function isAccountIdExist (id: number | string, res: Response, sendNotFound = tr | |||
28 | promise = AccountModel.loadByUUID('' + id) | 27 | promise = AccountModel.loadByUUID('' + id) |
29 | } | 28 | } |
30 | 29 | ||
31 | return isAccountExist(promise, res, sendNotFound) | 30 | return doesAccountExist(promise, res, sendNotFound) |
32 | } | 31 | } |
33 | 32 | ||
34 | function isLocalAccountNameExist (name: string, res: Response, sendNotFound = true) { | 33 | function doesLocalAccountNameExist (name: string, res: Response, sendNotFound = true) { |
35 | const promise = AccountModel.loadLocalByName(name) | 34 | const promise = AccountModel.loadLocalByName(name) |
36 | 35 | ||
37 | return isAccountExist(promise, res, sendNotFound) | 36 | return doesAccountExist(promise, res, sendNotFound) |
38 | } | 37 | } |
39 | 38 | ||
40 | function isAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { | 39 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { |
41 | const [ accountName, host ] = nameWithDomain.split('@') | 40 | return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound) |
42 | |||
43 | let promise: Bluebird<AccountModel> | ||
44 | if (!host || host === CONFIG.WEBSERVER.HOST) promise = AccountModel.loadLocalByName(accountName) | ||
45 | else promise = AccountModel.loadByNameAndHost(accountName, host) | ||
46 | |||
47 | return isAccountExist(promise, res, sendNotFound) | ||
48 | } | 41 | } |
49 | 42 | ||
50 | async function isAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { | 43 | async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { |
51 | const account = await p | 44 | const account = await p |
52 | 45 | ||
53 | if (!account) { | 46 | if (!account) { |
@@ -69,9 +62,9 @@ async function isAccountExist (p: Bluebird<AccountModel>, res: Response, sendNot | |||
69 | 62 | ||
70 | export { | 63 | export { |
71 | isAccountIdValid, | 64 | isAccountIdValid, |
72 | isAccountIdExist, | 65 | doesAccountIdExist, |
73 | isLocalAccountNameExist, | 66 | doesLocalAccountNameExist, |
74 | isAccountDescriptionValid, | 67 | isAccountDescriptionValid, |
75 | isAccountNameWithHostExist, | 68 | doesAccountNameWithHostExist, |
76 | isAccountNameValid | 69 | isAccountNameValid |
77 | } | 70 | } |
diff --git a/server/helpers/custom-validators/activitypub/activity.ts b/server/helpers/custom-validators/activitypub/activity.ts index b24590d9d..e0d170d9d 100644 --- a/server/helpers/custom-validators/activitypub/activity.ts +++ b/server/helpers/custom-validators/activitypub/activity.ts | |||
@@ -9,6 +9,7 @@ import { isViewActivityValid } from './view' | |||
9 | import { exists } from '../misc' | 9 | import { exists } from '../misc' |
10 | import { isCacheFileObjectValid } from './cache-file' | 10 | import { isCacheFileObjectValid } from './cache-file' |
11 | import { isFlagActivityValid } from './flag' | 11 | import { isFlagActivityValid } from './flag' |
12 | import { isPlaylistObjectValid } from './playlist' | ||
12 | 13 | ||
13 | function isRootActivityValid (activity: any) { | 14 | function isRootActivityValid (activity: any) { |
14 | return Array.isArray(activity['@context']) && ( | 15 | return Array.isArray(activity['@context']) && ( |
@@ -78,6 +79,7 @@ function checkCreateActivity (activity: any) { | |||
78 | isViewActivityValid(activity.object) || | 79 | isViewActivityValid(activity.object) || |
79 | isDislikeActivityValid(activity.object) || | 80 | isDislikeActivityValid(activity.object) || |
80 | isFlagActivityValid(activity.object) || | 81 | isFlagActivityValid(activity.object) || |
82 | isPlaylistObjectValid(activity.object) || | ||
81 | 83 | ||
82 | isCacheFileObjectValid(activity.object) || | 84 | isCacheFileObjectValid(activity.object) || |
83 | sanitizeAndCheckVideoCommentObject(activity.object) || | 85 | sanitizeAndCheckVideoCommentObject(activity.object) || |
@@ -89,6 +91,7 @@ function checkUpdateActivity (activity: any) { | |||
89 | return isBaseActivityValid(activity, 'Update') && | 91 | return isBaseActivityValid(activity, 'Update') && |
90 | ( | 92 | ( |
91 | isCacheFileObjectValid(activity.object) || | 93 | isCacheFileObjectValid(activity.object) || |
94 | isPlaylistObjectValid(activity.object) || | ||
92 | sanitizeAndCheckVideoTorrentObject(activity.object) || | 95 | sanitizeAndCheckVideoTorrentObject(activity.object) || |
93 | sanitizeAndCheckActorObject(activity.object) | 96 | sanitizeAndCheckActorObject(activity.object) |
94 | ) | 97 | ) |
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index c05f60f14..deb331abb 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 2 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
3 | import { exists, isArray } from '../misc' | 3 | import { exists, isArray } from '../misc' |
4 | import { truncate } from 'lodash' | 4 | import { truncate } from 'lodash' |
5 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | 5 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
diff --git a/server/helpers/custom-validators/activitypub/cache-file.ts b/server/helpers/custom-validators/activitypub/cache-file.ts index e2bd0c55e..21d5c53ca 100644 --- a/server/helpers/custom-validators/activitypub/cache-file.ts +++ b/server/helpers/custom-validators/activitypub/cache-file.ts | |||
@@ -8,9 +8,19 @@ function isCacheFileObjectValid (object: CacheFileObject) { | |||
8 | object.type === 'CacheFile' && | 8 | object.type === 'CacheFile' && |
9 | isDateValid(object.expires) && | 9 | isDateValid(object.expires) && |
10 | isActivityPubUrlValid(object.object) && | 10 | isActivityPubUrlValid(object.object) && |
11 | isRemoteVideoUrlValid(object.url) | 11 | (isRemoteVideoUrlValid(object.url) || isPlaylistRedundancyUrlValid(object.url)) |
12 | } | 12 | } |
13 | 13 | ||
14 | // --------------------------------------------------------------------------- | ||
15 | |||
14 | export { | 16 | export { |
15 | isCacheFileObjectValid | 17 | isCacheFileObjectValid |
16 | } | 18 | } |
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | function isPlaylistRedundancyUrlValid (url: any) { | ||
23 | return url.type === 'Link' && | ||
24 | (url.mediaType || url.mimeType) === 'application/x-mpegURL' && | ||
25 | isActivityPubUrlValid(url.href) | ||
26 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/misc.ts b/server/helpers/custom-validators/activitypub/misc.ts index f1762d11c..5afcfbedc 100644 --- a/server/helpers/custom-validators/activitypub/misc.ts +++ b/server/helpers/custom-validators/activitypub/misc.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 2 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
3 | import { isTestInstance } from '../../core-utils' | 3 | import { isTestInstance } from '../../core-utils' |
4 | import { exists } from '../misc' | 4 | import { exists } from '../misc' |
5 | 5 | ||
@@ -25,8 +25,7 @@ function isActivityPubUrlValid (url: string) { | |||
25 | } | 25 | } |
26 | 26 | ||
27 | function isBaseActivityValid (activity: any, type: string) { | 27 | function isBaseActivityValid (activity: any, type: string) { |
28 | return (activity['@context'] === undefined || Array.isArray(activity['@context'])) && | 28 | return activity.type === type && |
29 | activity.type === type && | ||
30 | isActivityPubUrlValid(activity.id) && | 29 | isActivityPubUrlValid(activity.id) && |
31 | isObjectValid(activity.actor) && | 30 | isObjectValid(activity.actor) && |
32 | isUrlCollectionValid(activity.to) && | 31 | isUrlCollectionValid(activity.to) && |
diff --git a/server/helpers/custom-validators/activitypub/playlist.ts b/server/helpers/custom-validators/activitypub/playlist.ts new file mode 100644 index 000000000..6c7bdb193 --- /dev/null +++ b/server/helpers/custom-validators/activitypub/playlist.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { exists, isDateValid } from '../misc' | ||
2 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | ||
3 | import * as validator from 'validator' | ||
4 | import { PlaylistElementObject } from '../../../../shared/models/activitypub/objects/playlist-element-object' | ||
5 | import { isActivityPubUrlValid } from './misc' | ||
6 | |||
7 | function isPlaylistObjectValid (object: PlaylistObject) { | ||
8 | return exists(object) && | ||
9 | object.type === 'Playlist' && | ||
10 | validator.isInt(object.totalItems + '') && | ||
11 | isDateValid(object.published) && | ||
12 | isDateValid(object.updated) | ||
13 | } | ||
14 | |||
15 | function isPlaylistElementObjectValid (object: PlaylistElementObject) { | ||
16 | return exists(object) && | ||
17 | object.type === 'PlaylistElement' && | ||
18 | validator.isInt(object.position + '') && | ||
19 | isActivityPubUrlValid(object.url) | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | isPlaylistObjectValid, | ||
26 | isPlaylistElementObjectValid | ||
27 | } | ||
diff --git a/server/helpers/custom-validators/activitypub/video-comments.ts b/server/helpers/custom-validators/activitypub/video-comments.ts index 0415db21c..26c8c4cc6 100644 --- a/server/helpers/custom-validators/activitypub/video-comments.ts +++ b/server/helpers/custom-validators/activitypub/video-comments.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers' | 2 | import { ACTIVITY_PUB } from '../../../initializers/constants' |
3 | import { exists, isArray, isDateValid } from '../misc' | 3 | import { exists, isArray, isDateValid } from '../misc' |
4 | import { isActivityPubUrlValid, isBaseActivityValid } from './misc' | 4 | import { isActivityPubUrlValid } from './misc' |
5 | 5 | ||
6 | function sanitizeAndCheckVideoCommentObject (comment: any) { | 6 | function sanitizeAndCheckVideoCommentObject (comment: any) { |
7 | if (!comment || comment.type !== 'Note') return false | 7 | if (!comment || comment.type !== 'Note') return false |
diff --git a/server/helpers/custom-validators/activitypub/videos.ts b/server/helpers/custom-validators/activitypub/videos.ts index 0f34aab21..3ba6b0744 100644 --- a/server/helpers/custom-validators/activitypub/videos.ts +++ b/server/helpers/custom-validators/activitypub/videos.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as validator from 'validator' | 1 | import * as validator from 'validator' |
2 | import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers' | 2 | import { ACTIVITY_PUB, CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
3 | import { peertubeTruncate } from '../../core-utils' | 3 | import { peertubeTruncate } from '../../core-utils' |
4 | import { exists, isBooleanValid, isDateValid, isUUIDValid } from '../misc' | 4 | import { exists, isArray, isBooleanValid, isDateValid, isUUIDValid } from '../misc' |
5 | import { | 5 | import { |
6 | isVideoDurationValid, | 6 | isVideoDurationValid, |
7 | isVideoNameValid, | 7 | isVideoNameValid, |
@@ -12,7 +12,6 @@ import { | |||
12 | } from '../videos' | 12 | } from '../videos' |
13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' | 13 | import { isActivityPubUrlValid, isBaseActivityValid, setValidAttributedTo } from './misc' |
14 | import { VideoState } from '../../../../shared/models/videos' | 14 | import { VideoState } from '../../../../shared/models/videos' |
15 | import { isVideoAbuseReasonValid } from '../video-abuses' | ||
16 | 15 | ||
17 | function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { | 16 | function sanitizeAndCheckVideoTorrentUpdateActivity (activity: any) { |
18 | return isBaseActivityValid(activity, 'Update') && | 17 | return isBaseActivityValid(activity, 'Update') && |
@@ -40,6 +39,7 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
40 | // Default attributes | 39 | // Default attributes |
41 | if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED | 40 | if (!isVideoStateValid(video.state)) video.state = VideoState.PUBLISHED |
42 | if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false | 41 | if (!isBooleanValid(video.waitTranscoding)) video.waitTranscoding = false |
42 | if (!isBooleanValid(video.downloadEnabled)) video.downloadEnabled = true | ||
43 | 43 | ||
44 | return isActivityPubUrlValid(video.id) && | 44 | return isActivityPubUrlValid(video.id) && |
45 | isVideoNameValid(video.name) && | 45 | isVideoNameValid(video.name) && |
@@ -51,8 +51,10 @@ function sanitizeAndCheckVideoTorrentObject (video: any) { | |||
51 | isVideoViewsValid(video.views) && | 51 | isVideoViewsValid(video.views) && |
52 | isBooleanValid(video.sensitive) && | 52 | isBooleanValid(video.sensitive) && |
53 | isBooleanValid(video.commentsEnabled) && | 53 | isBooleanValid(video.commentsEnabled) && |
54 | isBooleanValid(video.downloadEnabled) && | ||
54 | isDateValid(video.published) && | 55 | isDateValid(video.published) && |
55 | isDateValid(video.updated) && | 56 | isDateValid(video.updated) && |
57 | (!video.originallyPublishedAt || isDateValid(video.originallyPublishedAt)) && | ||
56 | (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && | 58 | (!video.content || isRemoteVideoContentValid(video.mediaType, video.content)) && |
57 | isRemoteVideoIconValid(video.icon) && | 59 | isRemoteVideoIconValid(video.icon) && |
58 | video.url.length !== 0 && | 60 | video.url.length !== 0 && |
@@ -81,6 +83,11 @@ function isRemoteVideoUrlValid (url: any) { | |||
81 | ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mediaType || url.mimeType) !== -1 && | 83 | ACTIVITY_PUB.URL_MIME_TYPES.MAGNET.indexOf(url.mediaType || url.mimeType) !== -1 && |
82 | validator.isLength(url.href, { min: 5 }) && | 84 | validator.isLength(url.href, { min: 5 }) && |
83 | validator.isInt(url.height + '', { min: 0 }) | 85 | validator.isInt(url.height + '', { min: 0 }) |
86 | ) || | ||
87 | ( | ||
88 | (url.mediaType || url.mimeType) === 'application/x-mpegURL' && | ||
89 | isActivityPubUrlValid(url.href) && | ||
90 | isArray(url.tag) | ||
84 | ) | 91 | ) |
85 | } | 92 | } |
86 | 93 | ||
diff --git a/server/helpers/custom-validators/logs.ts b/server/helpers/custom-validators/logs.ts new file mode 100644 index 000000000..30d0ce262 --- /dev/null +++ b/server/helpers/custom-validators/logs.ts | |||
@@ -0,0 +1,14 @@ | |||
1 | import { exists } from './misc' | ||
2 | import { LogLevel } from '../../../shared/models/server/log-level.type' | ||
3 | |||
4 | const logLevels: LogLevel[] = [ 'debug', 'info', 'warn', 'error' ] | ||
5 | |||
6 | function isValidLogLevel (value: any) { | ||
7 | return exists(value) && logLevels.indexOf(value) !== -1 | ||
8 | } | ||
9 | |||
10 | // --------------------------------------------------------------------------- | ||
11 | |||
12 | export { | ||
13 | isValidLogLevel | ||
14 | } | ||
diff --git a/server/helpers/custom-validators/misc.ts b/server/helpers/custom-validators/misc.ts index b6f0ebe6f..3a3deab0c 100644 --- a/server/helpers/custom-validators/misc.ts +++ b/server/helpers/custom-validators/misc.ts | |||
@@ -13,6 +13,10 @@ function isNotEmptyIntArray (value: any) { | |||
13 | return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 | 13 | return Array.isArray(value) && value.every(v => validator.isInt('' + v)) && value.length !== 0 |
14 | } | 14 | } |
15 | 15 | ||
16 | function isArrayOf (value: any, validator: (value: any) => boolean) { | ||
17 | return isArray(value) && value.every(v => validator(v)) | ||
18 | } | ||
19 | |||
16 | function isDateValid (value: string) { | 20 | function isDateValid (value: string) { |
17 | return exists(value) && validator.isISO8601(value) | 21 | return exists(value) && validator.isISO8601(value) |
18 | } | 22 | } |
@@ -45,12 +49,19 @@ function toValueOrNull (value: string) { | |||
45 | return value | 49 | return value |
46 | } | 50 | } |
47 | 51 | ||
48 | function toArray (value: string) { | 52 | function toArray (value: any) { |
49 | if (value && isArray(value) === false) return [ value ] | 53 | if (value && isArray(value) === false) return [ value ] |
50 | 54 | ||
51 | return value | 55 | return value |
52 | } | 56 | } |
53 | 57 | ||
58 | function toIntArray (value: any) { | ||
59 | if (!value) return [] | ||
60 | if (isArray(value) === false) return [ validator.toInt(value) ] | ||
61 | |||
62 | return value.map(v => validator.toInt(v)) | ||
63 | } | ||
64 | |||
54 | function isFileValid ( | 65 | function isFileValid ( |
55 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], | 66 | files: { [ fieldname: string ]: Express.Multer.File[] } | Express.Multer.File[], |
56 | mimeTypeRegex: string, | 67 | mimeTypeRegex: string, |
@@ -82,6 +93,7 @@ function isFileValid ( | |||
82 | 93 | ||
83 | export { | 94 | export { |
84 | exists, | 95 | exists, |
96 | isArrayOf, | ||
85 | isNotEmptyIntArray, | 97 | isNotEmptyIntArray, |
86 | isArray, | 98 | isArray, |
87 | isIdValid, | 99 | isIdValid, |
@@ -92,5 +104,6 @@ export { | |||
92 | isBooleanValid, | 104 | isBooleanValid, |
93 | toIntOrNull, | 105 | toIntOrNull, |
94 | toArray, | 106 | toArray, |
107 | toIntArray, | ||
95 | isFileValid | 108 | isFileValid |
96 | } | 109 | } |
diff --git a/server/helpers/custom-validators/servers.ts b/server/helpers/custom-validators/servers.ts index 18c80ec8f..5c8bf0d2d 100644 --- a/server/helpers/custom-validators/servers.ts +++ b/server/helpers/custom-validators/servers.ts | |||
@@ -3,7 +3,7 @@ import 'express-validator' | |||
3 | 3 | ||
4 | import { isArray, exists } from './misc' | 4 | import { isArray, exists } from './misc' |
5 | import { isTestInstance } from '../core-utils' | 5 | import { isTestInstance } from '../core-utils' |
6 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 6 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
7 | 7 | ||
8 | function isHostValid (host: string) { | 8 | function isHostValid (host: string) { |
9 | const isURLOptions = { | 9 | const isURLOptions = { |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index 80652b479..56bc10b16 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { UserRole } from '../../../shared' | 3 | import { UserRole } from '../../../shared' |
4 | import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers' | 4 | import { CONSTRAINTS_FIELDS, NSFW_POLICY_TYPES } from '../../initializers/constants' |
5 | import { exists, isFileValid, isBooleanValid } from './misc' | 5 | import { exists, isBooleanValid, isFileValid } from './misc' |
6 | import { values } from 'lodash' | 6 | import { values } from 'lodash' |
7 | 7 | ||
8 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS | 8 | const USERS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.USERS |
@@ -54,6 +54,10 @@ function isUserAutoPlayVideoValid (value: any) { | |||
54 | return isBooleanValid(value) | 54 | return isBooleanValid(value) |
55 | } | 55 | } |
56 | 56 | ||
57 | function isUserAdminFlagsValid (value: any) { | ||
58 | return exists(value) && validator.isInt('' + value) | ||
59 | } | ||
60 | |||
57 | function isUserBlockedValid (value: any) { | 61 | function isUserBlockedValid (value: any) { |
58 | return isBooleanValid(value) | 62 | return isBooleanValid(value) |
59 | } | 63 | } |
@@ -85,6 +89,7 @@ export { | |||
85 | isUserVideoQuotaValid, | 89 | isUserVideoQuotaValid, |
86 | isUserVideoQuotaDailyValid, | 90 | isUserVideoQuotaDailyValid, |
87 | isUserUsernameValid, | 91 | isUserUsernameValid, |
92 | isUserAdminFlagsValid, | ||
88 | isUserEmailVerifiedValid, | 93 | isUserEmailVerifiedValid, |
89 | isUserNSFWPolicyValid, | 94 | isUserNSFWPolicyValid, |
90 | isUserWebTorrentEnabledValid, | 95 | isUserWebTorrentEnabledValid, |
diff --git a/server/helpers/custom-validators/video-abuses.ts b/server/helpers/custom-validators/video-abuses.ts index 290efb149..a61dcee1c 100644 --- a/server/helpers/custom-validators/video-abuses.ts +++ b/server/helpers/custom-validators/video-abuses.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers' | 3 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' |
4 | import { exists } from './misc' | 4 | import { exists } from './misc' |
5 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 5 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
6 | 6 | ||
@@ -18,7 +18,7 @@ function isVideoAbuseStateValid (value: string) { | |||
18 | return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined | 18 | return exists(value) && VIDEO_ABUSE_STATES[ value ] !== undefined |
19 | } | 19 | } |
20 | 20 | ||
21 | async function isVideoAbuseExist (abuseId: number, videoId: number, res: Response) { | 21 | async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { |
22 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) | 22 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) |
23 | 23 | ||
24 | if (videoAbuse === null) { | 24 | if (videoAbuse === null) { |
@@ -36,7 +36,7 @@ async function isVideoAbuseExist (abuseId: number, videoId: number, res: Respons | |||
36 | // --------------------------------------------------------------------------- | 36 | // --------------------------------------------------------------------------- |
37 | 37 | ||
38 | export { | 38 | export { |
39 | isVideoAbuseExist, | 39 | doesVideoAbuseExist, |
40 | isVideoAbuseStateValid, | 40 | isVideoAbuseStateValid, |
41 | isVideoAbuseReasonValid, | 41 | isVideoAbuseReasonValid, |
42 | isVideoAbuseModerationCommentValid | 42 | isVideoAbuseModerationCommentValid |
diff --git a/server/helpers/custom-validators/video-blacklist.ts b/server/helpers/custom-validators/video-blacklist.ts index b36b08d8b..3743f7023 100644 --- a/server/helpers/custom-validators/video-blacklist.ts +++ b/server/helpers/custom-validators/video-blacklist.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import * as validator from 'validator' | 2 | import * as validator from 'validator' |
3 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 3 | import { exists } from './misc' |
4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
4 | import { VideoBlacklistModel } from '../../models/video/video-blacklist' | 5 | import { VideoBlacklistModel } from '../../models/video/video-blacklist' |
6 | import { VideoBlacklistType } from '../../../shared/models/videos' | ||
5 | 7 | ||
6 | const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST | 8 | const VIDEO_BLACKLIST_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_BLACKLIST |
7 | 9 | ||
@@ -9,7 +11,7 @@ function isVideoBlacklistReasonValid (value: string) { | |||
9 | return value === null || validator.isLength(value, VIDEO_BLACKLIST_CONSTRAINTS_FIELDS.REASON) | 11 | return value === null || validator.isLength(value, VIDEO_BLACKLIST_CONSTRAINTS_FIELDS.REASON) |
10 | } | 12 | } |
11 | 13 | ||
12 | async function isVideoBlacklistExist (videoId: number, res: Response) { | 14 | async function doesVideoBlacklistExist (videoId: number, res: Response) { |
13 | const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId) | 15 | const videoBlacklist = await VideoBlacklistModel.loadByVideoId(videoId) |
14 | 16 | ||
15 | if (videoBlacklist === null) { | 17 | if (videoBlacklist === null) { |
@@ -24,9 +26,14 @@ async function isVideoBlacklistExist (videoId: number, res: Response) { | |||
24 | return true | 26 | return true |
25 | } | 27 | } |
26 | 28 | ||
29 | function isVideoBlacklistTypeValid (value: any) { | ||
30 | return exists(value) && validator.isInt('' + value) && VideoBlacklistType[value] !== undefined | ||
31 | } | ||
32 | |||
27 | // --------------------------------------------------------------------------- | 33 | // --------------------------------------------------------------------------- |
28 | 34 | ||
29 | export { | 35 | export { |
30 | isVideoBlacklistReasonValid, | 36 | isVideoBlacklistReasonValid, |
31 | isVideoBlacklistExist | 37 | isVideoBlacklistTypeValid, |
38 | doesVideoBlacklistExist | ||
32 | } | 39 | } |
diff --git a/server/helpers/custom-validators/video-captions.ts b/server/helpers/custom-validators/video-captions.ts index b33d90e18..3b6569a8a 100644 --- a/server/helpers/custom-validators/video-captions.ts +++ b/server/helpers/custom-validators/video-captions.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers' | 1 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_LANGUAGES } from '../../initializers/constants' |
2 | import { exists, isFileValid } from './misc' | 2 | import { exists, isFileValid } from './misc' |
3 | import { Response } from 'express' | 3 | import { Response } from 'express' |
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
@@ -16,7 +16,7 @@ function isVideoCaptionFile (files: { [ fieldname: string ]: Express.Multer.File | |||
16 | return isFileValid(files, videoCaptionTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) | 16 | return isFileValid(files, videoCaptionTypesRegex, field, CONSTRAINTS_FIELDS.VIDEO_CAPTIONS.CAPTION_FILE.FILE_SIZE.max) |
17 | } | 17 | } |
18 | 18 | ||
19 | async function isVideoCaptionExist (video: VideoModel, language: string, res: Response) { | 19 | async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) { |
20 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) | 20 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) |
21 | 21 | ||
22 | if (!videoCaption) { | 22 | if (!videoCaption) { |
@@ -36,5 +36,5 @@ async function isVideoCaptionExist (video: VideoModel, language: string, res: Re | |||
36 | export { | 36 | export { |
37 | isVideoCaptionFile, | 37 | isVideoCaptionFile, |
38 | isVideoCaptionLanguageValid, | 38 | isVideoCaptionLanguageValid, |
39 | isVideoCaptionExist | 39 | doesVideoCaptionExist |
40 | } | 40 | } |
diff --git a/server/helpers/custom-validators/video-channels.ts b/server/helpers/custom-validators/video-channels.ts index f13519c1d..fd56b9a70 100644 --- a/server/helpers/custom-validators/video-channels.ts +++ b/server/helpers/custom-validators/video-channels.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import 'multer' | 3 | import 'multer' |
4 | import * as validator from 'validator' | 4 | import * as validator from 'validator' |
5 | import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | 5 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
6 | import { VideoChannelModel } from '../../models/video/video-channel' | 6 | import { VideoChannelModel } from '../../models/video/video-channel' |
7 | import { exists } from './misc' | 7 | import { exists } from './misc' |
8 | 8 | ||
@@ -20,29 +20,25 @@ function isVideoChannelSupportValid (value: string) { | |||
20 | return value === null || (exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.SUPPORT)) | 20 | return value === null || (exists(value) && validator.isLength(value, VIDEO_CHANNELS_CONSTRAINTS_FIELDS.SUPPORT)) |
21 | } | 21 | } |
22 | 22 | ||
23 | async function isLocalVideoChannelNameExist (name: string, res: express.Response) { | 23 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { |
24 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | 24 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) |
25 | 25 | ||
26 | return processVideoChannelExist(videoChannel, res) | 26 | return processVideoChannelExist(videoChannel, res) |
27 | } | 27 | } |
28 | 28 | ||
29 | async function isVideoChannelIdExist (id: string, res: express.Response) { | 29 | async function doesVideoChannelIdExist (id: number | string, res: express.Response) { |
30 | let videoChannel: VideoChannelModel | 30 | let videoChannel: VideoChannelModel |
31 | if (validator.isInt(id)) { | 31 | if (validator.isInt('' + id)) { |
32 | videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | 32 | videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) |
33 | } else { // UUID | 33 | } else { // UUID |
34 | videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount(id) | 34 | videoChannel = await VideoChannelModel.loadByUUIDAndPopulateAccount('' + id) |
35 | } | 35 | } |
36 | 36 | ||
37 | return processVideoChannelExist(videoChannel, res) | 37 | return processVideoChannelExist(videoChannel, res) |
38 | } | 38 | } |
39 | 39 | ||
40 | async function isVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | 40 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { |
41 | const [ name, host ] = nameWithDomain.split('@') | 41 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) |
42 | let videoChannel: VideoChannelModel | ||
43 | |||
44 | if (!host || host === CONFIG.WEBSERVER.HOST) videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | ||
45 | else videoChannel = await VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) | ||
46 | 42 | ||
47 | return processVideoChannelExist(videoChannel, res) | 43 | return processVideoChannelExist(videoChannel, res) |
48 | } | 44 | } |
@@ -50,12 +46,12 @@ async function isVideoChannelNameWithHostExist (nameWithDomain: string, res: exp | |||
50 | // --------------------------------------------------------------------------- | 46 | // --------------------------------------------------------------------------- |
51 | 47 | ||
52 | export { | 48 | export { |
53 | isVideoChannelNameWithHostExist, | 49 | doesVideoChannelNameWithHostExist, |
54 | isLocalVideoChannelNameExist, | 50 | doesLocalVideoChannelNameExist, |
55 | isVideoChannelDescriptionValid, | 51 | isVideoChannelDescriptionValid, |
56 | isVideoChannelNameValid, | 52 | isVideoChannelNameValid, |
57 | isVideoChannelSupportValid, | 53 | isVideoChannelSupportValid, |
58 | isVideoChannelIdExist | 54 | doesVideoChannelIdExist |
59 | } | 55 | } |
60 | 56 | ||
61 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { | 57 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { |
diff --git a/server/helpers/custom-validators/video-comments.ts b/server/helpers/custom-validators/video-comments.ts index 2b3f66063..0707e2af2 100644 --- a/server/helpers/custom-validators/video-comments.ts +++ b/server/helpers/custom-validators/video-comments.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import 'multer' | 2 | import 'multer' |
3 | import * as validator from 'validator' | 3 | import * as validator from 'validator' |
4 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 4 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
5 | 5 | ||
6 | const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS | 6 | const VIDEO_COMMENTS_CONSTRAINTS_FIELDS = CONSTRAINTS_FIELDS.VIDEO_COMMENTS |
7 | 7 | ||
diff --git a/server/helpers/custom-validators/video-imports.ts b/server/helpers/custom-validators/video-imports.ts index ce9e9193c..f4235e2fa 100644 --- a/server/helpers/custom-validators/video-imports.ts +++ b/server/helpers/custom-validators/video-imports.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import 'multer' | 2 | import 'multer' |
3 | import * as validator from 'validator' | 3 | import * as validator from 'validator' |
4 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers' | 4 | import { CONSTRAINTS_FIELDS, MIMETYPES, VIDEO_IMPORT_STATES } from '../../initializers/constants' |
5 | import { exists, isFileValid } from './misc' | 5 | import { exists, isFileValid } from './misc' |
6 | import * as express from 'express' | 6 | import * as express from 'express' |
7 | import { VideoImportModel } from '../../models/video/video-import' | 7 | import { VideoImportModel } from '../../models/video/video-import' |
@@ -30,7 +30,7 @@ function isVideoImportTorrentFile (files: { [ fieldname: string ]: Express.Multe | |||
30 | return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) | 30 | return isFileValid(files, videoTorrentImportRegex, 'torrentfile', CONSTRAINTS_FIELDS.VIDEO_IMPORTS.TORRENT_FILE.FILE_SIZE.max, true) |
31 | } | 31 | } |
32 | 32 | ||
33 | async function isVideoImportExist (id: number, res: express.Response) { | 33 | async function doesVideoImportExist (id: number, res: express.Response) { |
34 | const videoImport = await VideoImportModel.loadAndPopulateVideo(id) | 34 | const videoImport = await VideoImportModel.loadAndPopulateVideo(id) |
35 | 35 | ||
36 | if (!videoImport) { | 36 | if (!videoImport) { |
@@ -50,6 +50,6 @@ async function isVideoImportExist (id: number, res: express.Response) { | |||
50 | export { | 50 | export { |
51 | isVideoImportStateValid, | 51 | isVideoImportStateValid, |
52 | isVideoImportTargetUrlValid, | 52 | isVideoImportTargetUrlValid, |
53 | isVideoImportExist, | 53 | doesVideoImportExist, |
54 | isVideoImportTorrentFile | 54 | isVideoImportTorrentFile |
55 | } | 55 | } |
diff --git a/server/helpers/custom-validators/video-playlists.ts b/server/helpers/custom-validators/video-playlists.ts new file mode 100644 index 000000000..2fe426560 --- /dev/null +++ b/server/helpers/custom-validators/video-playlists.ts | |||
@@ -0,0 +1,55 @@ | |||
1 | import { exists } from './misc' | ||
2 | import * as validator from 'validator' | ||
3 | import { CONSTRAINTS_FIELDS, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PLAYLIST_TYPES } from '../../initializers/constants' | ||
4 | import * as express from 'express' | ||
5 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
6 | |||
7 | const PLAYLISTS_CONSTRAINT_FIELDS = CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS | ||
8 | |||
9 | function isVideoPlaylistNameValid (value: any) { | ||
10 | return exists(value) && validator.isLength(value, PLAYLISTS_CONSTRAINT_FIELDS.NAME) | ||
11 | } | ||
12 | |||
13 | function isVideoPlaylistDescriptionValid (value: any) { | ||
14 | return value === null || (exists(value) && validator.isLength(value, PLAYLISTS_CONSTRAINT_FIELDS.DESCRIPTION)) | ||
15 | } | ||
16 | |||
17 | function isVideoPlaylistPrivacyValid (value: number) { | ||
18 | return validator.isInt(value + '') && VIDEO_PLAYLIST_PRIVACIES[ value ] !== undefined | ||
19 | } | ||
20 | |||
21 | function isVideoPlaylistTimestampValid (value: any) { | ||
22 | return value === null || (exists(value) && validator.isInt('' + value, { min: 0 })) | ||
23 | } | ||
24 | |||
25 | function isVideoPlaylistTypeValid (value: any) { | ||
26 | return exists(value) && VIDEO_PLAYLIST_TYPES[ value ] !== undefined | ||
27 | } | ||
28 | |||
29 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') { | ||
30 | const videoPlaylist = fetchType === 'summary' | ||
31 | ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) | ||
32 | : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | ||
33 | |||
34 | if (!videoPlaylist) { | ||
35 | res.status(404) | ||
36 | .json({ error: 'Video playlist not found' }) | ||
37 | .end() | ||
38 | |||
39 | return false | ||
40 | } | ||
41 | |||
42 | res.locals.videoPlaylist = videoPlaylist | ||
43 | return true | ||
44 | } | ||
45 | |||
46 | // --------------------------------------------------------------------------- | ||
47 | |||
48 | export { | ||
49 | doesVideoPlaylistExist, | ||
50 | isVideoPlaylistNameValid, | ||
51 | isVideoPlaylistDescriptionValid, | ||
52 | isVideoPlaylistPrivacyValid, | ||
53 | isVideoPlaylistTimestampValid, | ||
54 | isVideoPlaylistTypeValid | ||
55 | } | ||
diff --git a/server/helpers/custom-validators/video-rates.ts b/server/helpers/custom-validators/video-rates.ts new file mode 100644 index 000000000..f2b6f7cae --- /dev/null +++ b/server/helpers/custom-validators/video-rates.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | function isRatingValid (value: any) { | ||
2 | return value === 'like' || value === 'dislike' | ||
3 | } | ||
4 | |||
5 | export { isRatingValid } | ||
diff --git a/server/helpers/custom-validators/videos.ts b/server/helpers/custom-validators/videos.ts index 95e256b8f..214db17a1 100644 --- a/server/helpers/custom-validators/videos.ts +++ b/server/helpers/custom-validators/videos.ts | |||
@@ -5,15 +5,16 @@ import 'multer' | |||
5 | import * as validator from 'validator' | 5 | import * as validator from 'validator' |
6 | import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' | 6 | import { UserRight, VideoFilter, VideoPrivacy, VideoRateType } from '../../../shared' |
7 | import { | 7 | import { |
8 | CONSTRAINTS_FIELDS, MIMETYPES, | 8 | CONSTRAINTS_FIELDS, |
9 | MIMETYPES, | ||
9 | VIDEO_CATEGORIES, | 10 | VIDEO_CATEGORIES, |
10 | VIDEO_LICENCES, | 11 | VIDEO_LICENCES, |
11 | VIDEO_PRIVACIES, | 12 | VIDEO_PRIVACIES, |
12 | VIDEO_RATE_TYPES, | 13 | VIDEO_RATE_TYPES, |
13 | VIDEO_STATES | 14 | VIDEO_STATES |
14 | } from '../../initializers' | 15 | } from '../../initializers/constants' |
15 | import { VideoModel } from '../../models/video/video' | 16 | import { VideoModel } from '../../models/video/video' |
16 | import { exists, isArray, isFileValid } from './misc' | 17 | import { exists, isArray, isDateValid, isFileValid } from './misc' |
17 | import { VideoChannelModel } from '../../models/video/video-channel' | 18 | import { VideoChannelModel } from '../../models/video/video-channel' |
18 | import { UserModel } from '../../models/account/user' | 19 | import { UserModel } from '../../models/account/user' |
19 | import * as magnetUtil from 'magnet-uri' | 20 | import * as magnetUtil from 'magnet-uri' |
@@ -115,6 +116,10 @@ function isScheduleVideoUpdatePrivacyValid (value: number) { | |||
115 | ) | 116 | ) |
116 | } | 117 | } |
117 | 118 | ||
119 | function isVideoOriginallyPublishedAtValid (value: string | null) { | ||
120 | return value === null || isDateValid(value) | ||
121 | } | ||
122 | |||
118 | function isVideoFileInfoHashValid (value: string | null | undefined) { | 123 | function isVideoFileInfoHashValid (value: string | null | undefined) { |
119 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH) | 124 | return exists(value) && validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH) |
120 | } | 125 | } |
@@ -161,7 +166,7 @@ function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: Use | |||
161 | return true | 166 | return true |
162 | } | 167 | } |
163 | 168 | ||
164 | async function isVideoExist (id: string, res: Response, fetchType: VideoFetchType = 'all') { | 169 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { |
165 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | 170 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined |
166 | 171 | ||
167 | const video = await fetchVideo(id, fetchType, userId) | 172 | const video = await fetchVideo(id, fetchType, userId) |
@@ -178,7 +183,7 @@ async function isVideoExist (id: string, res: Response, fetchType: VideoFetchTyp | |||
178 | return true | 183 | return true |
179 | } | 184 | } |
180 | 185 | ||
181 | async function isVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { | 186 | async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { |
182 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | 187 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { |
183 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | 188 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) |
184 | if (videoChannel === null) { | 189 | if (videoChannel === null) { |
@@ -220,6 +225,7 @@ export { | |||
220 | isVideoTagsValid, | 225 | isVideoTagsValid, |
221 | isVideoFPSResolutionValid, | 226 | isVideoFPSResolutionValid, |
222 | isScheduleVideoUpdatePrivacyValid, | 227 | isScheduleVideoUpdatePrivacyValid, |
228 | isVideoOriginallyPublishedAtValid, | ||
223 | isVideoFile, | 229 | isVideoFile, |
224 | isVideoMagnetUriValid, | 230 | isVideoMagnetUriValid, |
225 | isVideoStateValid, | 231 | isVideoStateValid, |
@@ -231,9 +237,9 @@ export { | |||
231 | isVideoPrivacyValid, | 237 | isVideoPrivacyValid, |
232 | isVideoFileResolutionValid, | 238 | isVideoFileResolutionValid, |
233 | isVideoFileSizeValid, | 239 | isVideoFileSizeValid, |
234 | isVideoExist, | 240 | doesVideoExist, |
235 | isVideoImage, | 241 | isVideoImage, |
236 | isVideoChannelOfAccountExist, | 242 | doesVideoChannelOfAccountExist, |
237 | isVideoSupportValid, | 243 | isVideoSupportValid, |
238 | isVideoFilterValid | 244 | isVideoFilterValid |
239 | } | 245 | } |
diff --git a/server/helpers/custom-validators/webfinger.ts b/server/helpers/custom-validators/webfinger.ts index 80a7e4a9d..dd914341e 100644 --- a/server/helpers/custom-validators/webfinger.ts +++ b/server/helpers/custom-validators/webfinger.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { CONFIG, REMOTE_SCHEME } from '../../initializers' | 1 | import { REMOTE_SCHEME, WEBSERVER } from '../../initializers/constants' |
2 | import { sanitizeHost } from '../core-utils' | 2 | import { sanitizeHost } from '../core-utils' |
3 | import { exists } from './misc' | 3 | import { exists } from './misc' |
4 | 4 | ||
@@ -11,7 +11,7 @@ function isWebfingerLocalResourceValid (value: string) { | |||
11 | if (actorParts.length !== 2) return false | 11 | if (actorParts.length !== 2) return false |
12 | 12 | ||
13 | const host = actorParts[1] | 13 | const host = actorParts[1] |
14 | return sanitizeHost(host, REMOTE_SCHEME.HTTP) === CONFIG.WEBSERVER.HOST | 14 | return sanitizeHost(host, REMOTE_SCHEME.HTTP) === WEBSERVER.HOST |
15 | } | 15 | } |
16 | 16 | ||
17 | // --------------------------------------------------------------------------- | 17 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/database-utils.ts b/server/helpers/database-utils.ts index 1005d2cf1..39c74b2fd 100644 --- a/server/helpers/database-utils.ts +++ b/server/helpers/database-utils.ts | |||
@@ -62,14 +62,13 @@ function updateInstanceWithAnother <T extends Model<T>> (instanceToUpdate: Model | |||
62 | const obj = baseInstance.toJSON() | 62 | const obj = baseInstance.toJSON() |
63 | 63 | ||
64 | for (const key of Object.keys(obj)) { | 64 | for (const key of Object.keys(obj)) { |
65 | instanceToUpdate.set(key, obj[key]) | 65 | instanceToUpdate[key] = obj[key] |
66 | } | 66 | } |
67 | } | 67 | } |
68 | 68 | ||
69 | function resetSequelizeInstance (instance: Model<any>, savedFields: object) { | 69 | function resetSequelizeInstance (instance: Model<any>, savedFields: object) { |
70 | Object.keys(savedFields).forEach(key => { | 70 | Object.keys(savedFields).forEach(key => { |
71 | const value = savedFields[key] | 71 | instance[key] = savedFields[key] |
72 | instance.set(key, value) | ||
73 | }) | 72 | }) |
74 | } | 73 | } |
75 | 74 | ||
diff --git a/server/helpers/express-utils.ts b/server/helpers/express-utils.ts index 9a72ee96d..e0a1d56a5 100644 --- a/server/helpers/express-utils.ts +++ b/server/helpers/express-utils.ts | |||
@@ -1,11 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as multer from 'multer' | 2 | import * as multer from 'multer' |
3 | import { CONFIG, REMOTE_SCHEME } from '../initializers' | 3 | import { REMOTE_SCHEME } from '../initializers/constants' |
4 | import { logger } from './logger' | 4 | import { logger } from './logger' |
5 | import { deleteFileAsync, generateRandomString } from './utils' | 5 | import { deleteFileAsync, generateRandomString } from './utils' |
6 | import { extname } from 'path' | 6 | import { extname } from 'path' |
7 | import { isArray } from './custom-validators/misc' | 7 | import { isArray } from './custom-validators/misc' |
8 | import { UserModel } from '../models/account/user' | 8 | import { CONFIG } from '../initializers/config' |
9 | 9 | ||
10 | function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { | 10 | function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { |
11 | if (paramNSFW === 'true') return true | 11 | if (paramNSFW === 'true') return true |
@@ -13,7 +13,7 @@ function buildNSFWFilter (res?: express.Response, paramNSFW?: string) { | |||
13 | if (paramNSFW === 'both') return undefined | 13 | if (paramNSFW === 'both') return undefined |
14 | 14 | ||
15 | if (res && res.locals.oauth) { | 15 | if (res && res.locals.oauth) { |
16 | const user: UserModel = res.locals.oauth.token.User | 16 | const user = res.locals.oauth.token.User |
17 | 17 | ||
18 | // User does not want NSFW videos | 18 | // User does not want NSFW videos |
19 | if (user.nsfwPolicy === 'do_not_list') return false | 19 | if (user.nsfwPolicy === 'do_not_list') return false |
@@ -59,7 +59,7 @@ function getHostWithPort (host: string) { | |||
59 | return host | 59 | return host |
60 | } | 60 | } |
61 | 61 | ||
62 | function badRequest (req: express.Request, res: express.Response, next: express.NextFunction) { | 62 | function badRequest (req: express.Request, res: express.Response) { |
63 | return res.type('json').status(400).end() | 63 | return res.type('json').status(400).end() |
64 | } | 64 | } |
65 | 65 | ||
@@ -100,7 +100,7 @@ function createReqFiles ( | |||
100 | } | 100 | } |
101 | 101 | ||
102 | function isUserAbleToSearchRemoteURI (res: express.Response) { | 102 | function isUserAbleToSearchRemoteURI (res: express.Response) { |
103 | const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined | 103 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
104 | 104 | ||
105 | return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true || | 105 | return CONFIG.SEARCH.REMOTE_URI.ANONYMOUS === true || |
106 | (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined) | 106 | (CONFIG.SEARCH.REMOTE_URI.USERS === true && user !== undefined) |
diff --git a/server/helpers/ffmpeg-utils.ts b/server/helpers/ffmpeg-utils.ts index 132f4690e..76b744de8 100644 --- a/server/helpers/ffmpeg-utils.ts +++ b/server/helpers/ffmpeg-utils.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | import * as ffmpeg from 'fluent-ffmpeg' | 1 | import * as ffmpeg from 'fluent-ffmpeg' |
2 | import { join } from 'path' | 2 | import { dirname, join } from 'path' |
3 | import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' | 3 | import { getTargetBitrate, VideoResolution } from '../../shared/models/videos' |
4 | import { CONFIG, FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' | 4 | import { FFMPEG_NICE, VIDEO_TRANSCODING_FPS } from '../initializers/constants' |
5 | import { processImage } from './image-utils' | 5 | import { processImage } from './image-utils' |
6 | import { logger } from './logger' | 6 | import { logger } from './logger' |
7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' | 7 | import { checkFFmpegEncoders } from '../initializers/checker-before-init' |
8 | import { remove } from 'fs-extra' | 8 | import { readFile, remove, writeFile } from 'fs-extra' |
9 | import { CONFIG } from '../initializers/config' | ||
9 | 10 | ||
10 | function computeResolutionsToTranscode (videoFileHeight: number) { | 11 | function computeResolutionsToTranscode (videoFileHeight: number) { |
11 | const resolutionsEnabled: number[] = [] | 12 | const resolutionsEnabled: number[] = [] |
@@ -29,12 +30,21 @@ function computeResolutionsToTranscode (videoFileHeight: number) { | |||
29 | return resolutionsEnabled | 30 | return resolutionsEnabled |
30 | } | 31 | } |
31 | 32 | ||
32 | async function getVideoFileResolution (path: string) { | 33 | async function getVideoFileSize (path: string) { |
33 | const videoStream = await getVideoFileStream(path) | 34 | const videoStream = await getVideoFileStream(path) |
34 | 35 | ||
35 | return { | 36 | return { |
36 | videoFileResolution: Math.min(videoStream.height, videoStream.width), | 37 | width: videoStream.width, |
37 | isPortraitMode: videoStream.height > videoStream.width | 38 | height: videoStream.height |
39 | } | ||
40 | } | ||
41 | |||
42 | async function getVideoFileResolution (path: string) { | ||
43 | const size = await getVideoFileSize(path) | ||
44 | |||
45 | return { | ||
46 | videoFileResolution: Math.min(size.height, size.width), | ||
47 | isPortraitMode: size.height > size.width | ||
38 | } | 48 | } |
39 | } | 49 | } |
40 | 50 | ||
@@ -95,7 +105,7 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima | |||
95 | }) | 105 | }) |
96 | 106 | ||
97 | const destination = join(folder, imageName) | 107 | const destination = join(folder, imageName) |
98 | await processImage({ path: pendingImagePath }, destination, size) | 108 | await processImage(pendingImagePath, destination, size) |
99 | } catch (err) { | 109 | } catch (err) { |
100 | logger.error('Cannot generate image from video %s.', fromPath, { err }) | 110 | logger.error('Cannot generate image from video %s.', fromPath, { err }) |
101 | 111 | ||
@@ -110,52 +120,41 @@ async function generateImageFromVideoFile (fromPath: string, folder: string, ima | |||
110 | type TranscodeOptions = { | 120 | type TranscodeOptions = { |
111 | inputPath: string | 121 | inputPath: string |
112 | outputPath: string | 122 | outputPath: string |
113 | resolution?: VideoResolution | 123 | resolution: VideoResolution |
114 | isPortraitMode?: boolean | 124 | isPortraitMode?: boolean |
125 | |||
126 | hlsPlaylist?: { | ||
127 | videoFilename: string | ||
128 | } | ||
115 | } | 129 | } |
116 | 130 | ||
117 | function transcode (options: TranscodeOptions) { | 131 | function transcode (options: TranscodeOptions) { |
118 | return new Promise<void>(async (res, rej) => { | 132 | return new Promise<void>(async (res, rej) => { |
119 | try { | 133 | try { |
120 | let fps = await getVideoFileFPS(options.inputPath) | ||
121 | // On small/medium resolutions, limit FPS | ||
122 | if ( | ||
123 | options.resolution !== undefined && | ||
124 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | ||
125 | fps > VIDEO_TRANSCODING_FPS.AVERAGE | ||
126 | ) { | ||
127 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | ||
128 | } | ||
129 | |||
130 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) | 134 | let command = ffmpeg(options.inputPath, { niceness: FFMPEG_NICE.TRANSCODING }) |
131 | .output(options.outputPath) | 135 | .output(options.outputPath) |
132 | command = await presetH264(command, options.resolution, fps) | 136 | |
137 | if (options.hlsPlaylist) { | ||
138 | command = await buildHLSCommand(command, options) | ||
139 | } else { | ||
140 | command = await buildx264Command(command, options) | ||
141 | } | ||
133 | 142 | ||
134 | if (CONFIG.TRANSCODING.THREADS > 0) { | 143 | if (CONFIG.TRANSCODING.THREADS > 0) { |
135 | // if we don't set any threads ffmpeg will chose automatically | 144 | // if we don't set any threads ffmpeg will chose automatically |
136 | command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) | 145 | command = command.outputOption('-threads ' + CONFIG.TRANSCODING.THREADS) |
137 | } | 146 | } |
138 | 147 | ||
139 | if (options.resolution !== undefined) { | ||
140 | // '?x720' or '720x?' for example | ||
141 | const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` | ||
142 | command = command.size(size) | ||
143 | } | ||
144 | |||
145 | if (fps) { | ||
146 | // Hard FPS limits | ||
147 | if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX | ||
148 | else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN | ||
149 | |||
150 | command = command.withFPS(fps) | ||
151 | } | ||
152 | |||
153 | command | 148 | command |
154 | .on('error', (err, stdout, stderr) => { | 149 | .on('error', (err, stdout, stderr) => { |
155 | logger.error('Error in transcoding job.', { stdout, stderr }) | 150 | logger.error('Error in transcoding job.', { stdout, stderr }) |
156 | return rej(err) | 151 | return rej(err) |
157 | }) | 152 | }) |
158 | .on('end', res) | 153 | .on('end', () => { |
154 | return onTranscodingSuccess(options) | ||
155 | .then(() => res()) | ||
156 | .catch(err => rej(err)) | ||
157 | }) | ||
159 | .run() | 158 | .run() |
160 | } catch (err) { | 159 | } catch (err) { |
161 | return rej(err) | 160 | return rej(err) |
@@ -166,6 +165,7 @@ function transcode (options: TranscodeOptions) { | |||
166 | // --------------------------------------------------------------------------- | 165 | // --------------------------------------------------------------------------- |
167 | 166 | ||
168 | export { | 167 | export { |
168 | getVideoFileSize, | ||
169 | getVideoFileResolution, | 169 | getVideoFileResolution, |
170 | getDurationFromVideoFile, | 170 | getDurationFromVideoFile, |
171 | generateImageFromVideoFile, | 171 | generateImageFromVideoFile, |
@@ -178,6 +178,71 @@ export { | |||
178 | 178 | ||
179 | // --------------------------------------------------------------------------- | 179 | // --------------------------------------------------------------------------- |
180 | 180 | ||
181 | async function buildx264Command (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { | ||
182 | let fps = await getVideoFileFPS(options.inputPath) | ||
183 | // On small/medium resolutions, limit FPS | ||
184 | if ( | ||
185 | options.resolution !== undefined && | ||
186 | options.resolution < VIDEO_TRANSCODING_FPS.KEEP_ORIGIN_FPS_RESOLUTION_MIN && | ||
187 | fps > VIDEO_TRANSCODING_FPS.AVERAGE | ||
188 | ) { | ||
189 | fps = VIDEO_TRANSCODING_FPS.AVERAGE | ||
190 | } | ||
191 | |||
192 | command = await presetH264(command, options.resolution, fps) | ||
193 | |||
194 | if (options.resolution !== undefined) { | ||
195 | // '?x720' or '720x?' for example | ||
196 | const size = options.isPortraitMode === true ? `${options.resolution}x?` : `?x${options.resolution}` | ||
197 | command = command.size(size) | ||
198 | } | ||
199 | |||
200 | if (fps) { | ||
201 | // Hard FPS limits | ||
202 | if (fps > VIDEO_TRANSCODING_FPS.MAX) fps = VIDEO_TRANSCODING_FPS.MAX | ||
203 | else if (fps < VIDEO_TRANSCODING_FPS.MIN) fps = VIDEO_TRANSCODING_FPS.MIN | ||
204 | |||
205 | command = command.withFPS(fps) | ||
206 | } | ||
207 | |||
208 | return command | ||
209 | } | ||
210 | |||
211 | async function buildHLSCommand (command: ffmpeg.FfmpegCommand, options: TranscodeOptions) { | ||
212 | const videoPath = getHLSVideoPath(options) | ||
213 | |||
214 | command = await presetCopy(command) | ||
215 | |||
216 | command = command.outputOption('-hls_time 4') | ||
217 | .outputOption('-hls_list_size 0') | ||
218 | .outputOption('-hls_playlist_type vod') | ||
219 | .outputOption('-hls_segment_filename ' + videoPath) | ||
220 | .outputOption('-hls_segment_type fmp4') | ||
221 | .outputOption('-f hls') | ||
222 | .outputOption('-hls_flags single_file') | ||
223 | |||
224 | return command | ||
225 | } | ||
226 | |||
227 | function getHLSVideoPath (options: TranscodeOptions) { | ||
228 | return `${dirname(options.outputPath)}/${options.hlsPlaylist.videoFilename}` | ||
229 | } | ||
230 | |||
231 | async function onTranscodingSuccess (options: TranscodeOptions) { | ||
232 | if (!options.hlsPlaylist) return | ||
233 | |||
234 | // Fix wrong mapping with some ffmpeg versions | ||
235 | const fileContent = await readFile(options.outputPath) | ||
236 | |||
237 | const videoFileName = options.hlsPlaylist.videoFilename | ||
238 | const videoFilePath = getHLSVideoPath(options) | ||
239 | |||
240 | const newContent = fileContent.toString() | ||
241 | .replace(`#EXT-X-MAP:URI="${videoFilePath}",`, `#EXT-X-MAP:URI="${videoFileName}",`) | ||
242 | |||
243 | await writeFile(options.outputPath, newContent) | ||
244 | } | ||
245 | |||
181 | function getVideoFileStream (path: string) { | 246 | function getVideoFileStream (path: string) { |
182 | return new Promise<any>((res, rej) => { | 247 | return new Promise<any>((res, rej) => { |
183 | ffmpeg.ffprobe(path, (err, metadata) => { | 248 | ffmpeg.ffprobe(path, (err, metadata) => { |
@@ -348,3 +413,10 @@ async function presetH264 (command: ffmpeg.FfmpegCommand, resolution: VideoResol | |||
348 | 413 | ||
349 | return localCommand | 414 | return localCommand |
350 | } | 415 | } |
416 | |||
417 | async function presetCopy (command: ffmpeg.FfmpegCommand): Promise<ffmpeg.FfmpegCommand> { | ||
418 | return command | ||
419 | .format('mp4') | ||
420 | .videoCodec('copy') | ||
421 | .audioCodec('copy') | ||
422 | } | ||
diff --git a/server/helpers/image-utils.ts b/server/helpers/image-utils.ts index e43ea3f1d..bd81aa3ba 100644 --- a/server/helpers/image-utils.ts +++ b/server/helpers/image-utils.ts | |||
@@ -4,18 +4,19 @@ import { readFile, remove } from 'fs-extra' | |||
4 | import { logger } from './logger' | 4 | import { logger } from './logger' |
5 | 5 | ||
6 | async function processImage ( | 6 | async function processImage ( |
7 | physicalFile: { path: string }, | 7 | path: string, |
8 | destination: string, | 8 | destination: string, |
9 | newSize: { width: number, height: number } | 9 | newSize: { width: number, height: number }, |
10 | keepOriginal = false | ||
10 | ) { | 11 | ) { |
11 | if (physicalFile.path === destination) { | 12 | if (path === destination) { |
12 | throw new Error('Sharp needs an input path different that the output path.') | 13 | throw new Error('Sharp needs an input path different that the output path.') |
13 | } | 14 | } |
14 | 15 | ||
15 | logger.debug('Processing image %s to %s.', physicalFile.path, destination) | 16 | logger.debug('Processing image %s to %s.', path, destination) |
16 | 17 | ||
17 | // Avoid sharp cache | 18 | // Avoid sharp cache |
18 | const buf = await readFile(physicalFile.path) | 19 | const buf = await readFile(path) |
19 | const sharpInstance = sharp(buf) | 20 | const sharpInstance = sharp(buf) |
20 | 21 | ||
21 | await remove(destination) | 22 | await remove(destination) |
@@ -24,7 +25,7 @@ async function processImage ( | |||
24 | .resize(newSize.width, newSize.height) | 25 | .resize(newSize.width, newSize.height) |
25 | .toFile(destination) | 26 | .toFile(destination) |
26 | 27 | ||
27 | await remove(physicalFile.path) | 28 | if (keepOriginal !== true) await remove(path) |
28 | } | 29 | } |
29 | 30 | ||
30 | // --------------------------------------------------------------------------- | 31 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/logger.ts b/server/helpers/logger.ts index 203e637a8..734523b01 100644 --- a/server/helpers/logger.ts +++ b/server/helpers/logger.ts | |||
@@ -2,11 +2,13 @@ | |||
2 | import { mkdirpSync } from 'fs-extra' | 2 | import { mkdirpSync } from 'fs-extra' |
3 | import * as path from 'path' | 3 | import * as path from 'path' |
4 | import * as winston from 'winston' | 4 | import * as winston from 'winston' |
5 | import { CONFIG } from '../initializers' | 5 | import { CONFIG } from '../initializers/config' |
6 | import { omit } from 'lodash' | ||
6 | 7 | ||
7 | const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | 8 | const label = CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT |
8 | 9 | ||
9 | // Create the directory if it does not exist | 10 | // Create the directory if it does not exist |
11 | // FIXME: use async | ||
10 | mkdirpSync(CONFIG.STORAGE.LOG_DIR) | 12 | mkdirpSync(CONFIG.STORAGE.LOG_DIR) |
11 | 13 | ||
12 | function loggerReplacer (key: string, value: any) { | 14 | function loggerReplacer (key: string, value: any) { |
@@ -22,13 +24,10 @@ function loggerReplacer (key: string, value: any) { | |||
22 | } | 24 | } |
23 | 25 | ||
24 | const consoleLoggerFormat = winston.format.printf(info => { | 26 | const consoleLoggerFormat = winston.format.printf(info => { |
25 | const obj = { | 27 | const obj = omit(info, 'label', 'timestamp', 'level', 'message') |
26 | meta: info.meta, | ||
27 | err: info.err, | ||
28 | sql: info.sql | ||
29 | } | ||
30 | 28 | ||
31 | let additionalInfos = JSON.stringify(obj, loggerReplacer, 2) | 29 | let additionalInfos = JSON.stringify(obj, loggerReplacer, 2) |
30 | |||
32 | if (additionalInfos === undefined || additionalInfos === '{}') additionalInfos = '' | 31 | if (additionalInfos === undefined || additionalInfos === '{}') additionalInfos = '' |
33 | else additionalInfos = ' ' + additionalInfos | 32 | else additionalInfos = ' ' + additionalInfos |
34 | 33 | ||
@@ -57,7 +56,7 @@ const logger = winston.createLogger({ | |||
57 | filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube.log'), | 56 | filename: path.join(CONFIG.STORAGE.LOG_DIR, 'peertube.log'), |
58 | handleExceptions: true, | 57 | handleExceptions: true, |
59 | maxsize: 1024 * 1024 * 12, | 58 | maxsize: 1024 * 1024 * 12, |
60 | maxFiles: 5, | 59 | maxFiles: 20, |
61 | format: winston.format.combine( | 60 | format: winston.format.combine( |
62 | winston.format.timestamp(), | 61 | winston.format.timestamp(), |
63 | jsonLoggerFormat | 62 | jsonLoggerFormat |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index ab9ec077e..9148df2eb 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { Request } from 'express' | 1 | import { Request } from 'express' |
2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers' | 2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' |
3 | import { ActorModel } from '../models/activitypub/actor' | 3 | import { ActorModel } from '../models/activitypub/actor' |
4 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils' | 4 | import { bcryptComparePromise, bcryptGenSaltPromise, bcryptHashPromise, createPrivateKey, getPublicKey, sha256 } from './core-utils' |
5 | import { jsig, jsonld } from './custom-jsonld-signature' | 5 | import { jsig, jsonld } from './custom-jsonld-signature' |
diff --git a/server/helpers/requests.ts b/server/helpers/requests.ts index 3fc776f1a..2e30c94a1 100644 --- a/server/helpers/requests.ts +++ b/server/helpers/requests.ts | |||
@@ -1,13 +1,16 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { createWriteStream } from 'fs-extra' | 2 | import { createWriteStream, remove } from 'fs-extra' |
3 | import * as request from 'request' | 3 | import * as request from 'request' |
4 | import { ACTIVITY_PUB, CONFIG } from '../initializers' | 4 | import { ACTIVITY_PUB } from '../initializers/constants' |
5 | import { processImage } from './image-utils' | 5 | import { processImage } from './image-utils' |
6 | import { join } from 'path' | 6 | import { join } from 'path' |
7 | import { logger } from './logger' | ||
8 | import { CONFIG } from '../initializers/config' | ||
7 | 9 | ||
8 | function doRequest <T> ( | 10 | function doRequest <T> ( |
9 | requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean } | 11 | requestOptions: request.CoreOptions & request.UriOptions & { activityPub?: boolean }, |
10 | ): Bluebird<{ response: request.RequestResponse, body: any }> { | 12 | bodyKBLimit = 1000 // 1MB |
13 | ): Bluebird<{ response: request.RequestResponse, body: T }> { | ||
11 | if (requestOptions.activityPub === true) { | 14 | if (requestOptions.activityPub === true) { |
12 | if (!Array.isArray(requestOptions.headers)) requestOptions.headers = {} | 15 | if (!Array.isArray(requestOptions.headers)) requestOptions.headers = {} |
13 | requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER | 16 | requestOptions.headers['accept'] = ACTIVITY_PUB.ACCEPT_HEADER |
@@ -15,16 +18,29 @@ function doRequest <T> ( | |||
15 | 18 | ||
16 | return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => { | 19 | return new Bluebird<{ response: request.RequestResponse, body: T }>((res, rej) => { |
17 | request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body })) | 20 | request(requestOptions, (err, response, body) => err ? rej(err) : res({ response, body })) |
21 | .on('data', onRequestDataLengthCheck(bodyKBLimit)) | ||
18 | }) | 22 | }) |
19 | } | 23 | } |
20 | 24 | ||
21 | function doRequestAndSaveToFile (requestOptions: request.CoreOptions & request.UriOptions, destPath: string) { | 25 | function doRequestAndSaveToFile ( |
26 | requestOptions: request.CoreOptions & request.UriOptions, | ||
27 | destPath: string, | ||
28 | bodyKBLimit = 10000 // 10MB | ||
29 | ) { | ||
22 | return new Bluebird<void>((res, rej) => { | 30 | return new Bluebird<void>((res, rej) => { |
23 | const file = createWriteStream(destPath) | 31 | const file = createWriteStream(destPath) |
24 | file.on('finish', () => res()) | 32 | file.on('finish', () => res()) |
25 | 33 | ||
26 | request(requestOptions) | 34 | request(requestOptions) |
27 | .on('error', err => rej(err)) | 35 | .on('data', onRequestDataLengthCheck(bodyKBLimit)) |
36 | .on('error', err => { | ||
37 | file.close() | ||
38 | |||
39 | remove(destPath) | ||
40 | .catch(err => logger.error('Cannot remove %s after request failure.', destPath, { err })) | ||
41 | |||
42 | return rej(err) | ||
43 | }) | ||
28 | .pipe(file) | 44 | .pipe(file) |
29 | }) | 45 | }) |
30 | } | 46 | } |
@@ -34,7 +50,14 @@ async function downloadImage (url: string, destDir: string, destName: string, si | |||
34 | await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath) | 50 | await doRequestAndSaveToFile({ method: 'GET', uri: url }, tmpPath) |
35 | 51 | ||
36 | const destPath = join(destDir, destName) | 52 | const destPath = join(destDir, destName) |
37 | await processImage({ path: tmpPath }, destPath, size) | 53 | |
54 | try { | ||
55 | await processImage(tmpPath, destPath, size) | ||
56 | } catch (err) { | ||
57 | await remove(tmpPath) | ||
58 | |||
59 | throw err | ||
60 | } | ||
38 | } | 61 | } |
39 | 62 | ||
40 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
@@ -44,3 +67,21 @@ export { | |||
44 | doRequestAndSaveToFile, | 67 | doRequestAndSaveToFile, |
45 | downloadImage | 68 | downloadImage |
46 | } | 69 | } |
70 | |||
71 | // --------------------------------------------------------------------------- | ||
72 | |||
73 | // Thanks to https://github.com/request/request/issues/2470#issuecomment-268929907 <3 | ||
74 | function onRequestDataLengthCheck (bodyKBLimit: number) { | ||
75 | let bufferLength = 0 | ||
76 | const bytesLimit = bodyKBLimit * 1000 | ||
77 | |||
78 | return function (chunk) { | ||
79 | bufferLength += chunk.length | ||
80 | if (bufferLength > bytesLimit) { | ||
81 | this.abort() | ||
82 | |||
83 | const error = new Error(`Response was too large - aborted after ${bytesLimit} bytes.`) | ||
84 | this.emit('error', error) | ||
85 | } | ||
86 | } | ||
87 | } | ||
diff --git a/server/helpers/signup.ts b/server/helpers/signup.ts index cdce7989d..5eb56b3cf 100644 --- a/server/helpers/signup.ts +++ b/server/helpers/signup.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { CONFIG } from '../initializers' | ||
2 | import { UserModel } from '../models/account/user' | 1 | import { UserModel } from '../models/account/user' |
3 | import * as ipaddr from 'ipaddr.js' | 2 | import * as ipaddr from 'ipaddr.js' |
3 | import { CONFIG } from '../initializers/config' | ||
4 | |||
4 | const isCidr = require('is-cidr') | 5 | const isCidr = require('is-cidr') |
5 | 6 | ||
6 | async function isSignupAllowed () { | 7 | async function isSignupAllowed () { |
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 3c3406e38..94ceb15e0 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import { ResultList } from '../../shared' | 1 | import { ResultList } from '../../shared' |
2 | import { CONFIG } from '../initializers' | ||
3 | import { ApplicationModel } from '../models/application/application' | 2 | import { ApplicationModel } from '../models/application/application' |
4 | import { execPromise, execPromise2, pseudoRandomBytesPromise, sha256 } from './core-utils' | 3 | import { execPromise, execPromise2, pseudoRandomBytesPromise, sha256 } from './core-utils' |
5 | import { logger } from './logger' | 4 | import { logger } from './logger' |
@@ -7,7 +6,7 @@ import { join } from 'path' | |||
7 | import { Instance as ParseTorrent } from 'parse-torrent' | 6 | import { Instance as ParseTorrent } from 'parse-torrent' |
8 | import { remove } from 'fs-extra' | 7 | import { remove } from 'fs-extra' |
9 | import * as memoizee from 'memoizee' | 8 | import * as memoizee from 'memoizee' |
10 | import { isArray } from './custom-validators/misc' | 9 | import { CONFIG } from '../initializers/config' |
11 | 10 | ||
12 | function deleteFileAsync (path: string) { | 11 | function deleteFileAsync (path: string) { |
13 | remove(path) | 12 | remove(path) |
diff --git a/server/helpers/video.ts b/server/helpers/video.ts index 1bd21467d..c90fe06c7 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts | |||
@@ -1,10 +1,12 @@ | |||
1 | import { VideoModel } from '../models/video/video' | 1 | import { VideoModel } from '../models/video/video' |
2 | 2 | ||
3 | type VideoFetchType = 'all' | 'only-video' | 'id' | 'none' | 3 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' |
4 | 4 | ||
5 | function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { | 5 | function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { |
6 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) | 6 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) |
7 | 7 | ||
8 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) | ||
9 | |||
8 | if (fetchType === 'only-video') return VideoModel.load(id) | 10 | if (fetchType === 'only-video') return VideoModel.load(id) |
9 | 11 | ||
10 | if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) | 12 | if (fetchType === 'id' || fetchType === 'none') return VideoModel.loadOnlyId(id) |
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index 156376943..049808846 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts | |||
@@ -3,7 +3,7 @@ import { WebFingerData } from '../../shared' | |||
3 | import { ActorModel } from '../models/activitypub/actor' | 3 | import { ActorModel } from '../models/activitypub/actor' |
4 | import { isTestInstance } from './core-utils' | 4 | import { isTestInstance } from './core-utils' |
5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' | 5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' |
6 | import { CONFIG } from '../initializers' | 6 | import { WEBSERVER } from '../initializers/constants' |
7 | 7 | ||
8 | const webfinger = new WebFinger({ | 8 | const webfinger = new WebFinger({ |
9 | webfist_fallback: false, | 9 | webfist_fallback: false, |
@@ -19,7 +19,7 @@ async function loadActorUrlOrGetFromWebfinger (uriArg: string) { | |||
19 | const [ name, host ] = uri.split('@') | 19 | const [ name, host ] = uri.split('@') |
20 | let actor: ActorModel | 20 | let actor: ActorModel |
21 | 21 | ||
22 | if (host === CONFIG.WEBSERVER.HOST) { | 22 | if (host === WEBSERVER.HOST) { |
23 | actor = await ActorModel.loadLocalByName(name) | 23 | actor = await ActorModel.loadLocalByName(name) |
24 | } else { | 24 | } else { |
25 | actor = await ActorModel.loadByNameAndHost(name, host) | 25 | actor = await ActorModel.loadByNameAndHost(name, host) |
diff --git a/server/helpers/webtorrent.ts b/server/helpers/webtorrent.ts index 3c9a0b96a..14dfe0d28 100644 --- a/server/helpers/webtorrent.ts +++ b/server/helpers/webtorrent.ts | |||
@@ -2,7 +2,7 @@ import { logger } from './logger' | |||
2 | import { generateVideoImportTmpPath } from './utils' | 2 | import { generateVideoImportTmpPath } from './utils' |
3 | import * as WebTorrent from 'webtorrent' | 3 | import * as WebTorrent from 'webtorrent' |
4 | import { createWriteStream, ensureDir, remove } from 'fs-extra' | 4 | import { createWriteStream, ensureDir, remove } from 'fs-extra' |
5 | import { CONFIG } from '../initializers' | 5 | import { CONFIG } from '../initializers/config' |
6 | import { dirname, join } from 'path' | 6 | import { dirname, join } from 'path' |
7 | 7 | ||
8 | async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName?: string }, timeout: number) { | 8 | async function downloadWebTorrentVideo (target: { magnetUri: string, torrentName?: string }, timeout: number) { |
diff --git a/server/helpers/youtube-dl.ts b/server/helpers/youtube-dl.ts index b74351b42..b3079370f 100644 --- a/server/helpers/youtube-dl.ts +++ b/server/helpers/youtube-dl.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { truncate } from 'lodash' | 1 | import { truncate } from 'lodash' |
2 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES } from '../initializers' | 2 | import { CONSTRAINTS_FIELDS, VIDEO_CATEGORIES } from '../initializers/constants' |
3 | import { logger } from './logger' | 3 | import { logger } from './logger' |
4 | import { generateVideoImportTmpPath } from './utils' | 4 | import { generateVideoImportTmpPath } from './utils' |
5 | import { join } from 'path' | 5 | import { join } from 'path' |
@@ -16,6 +16,7 @@ export type YoutubeDLInfo = { | |||
16 | nsfw?: boolean | 16 | nsfw?: boolean |
17 | tags?: string[] | 17 | tags?: string[] |
18 | thumbnailUrl?: string | 18 | thumbnailUrl?: string |
19 | originallyPublishedAt?: Date | ||
19 | } | 20 | } |
20 | 21 | ||
21 | const processOptions = { | 22 | const processOptions = { |
@@ -47,6 +48,11 @@ function downloadYoutubeDLVideo (url: string, timeout: number) { | |||
47 | 48 | ||
48 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] | 49 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] |
49 | 50 | ||
51 | if (process.env.FFMPEG_PATH) { | ||
52 | options.push('--ffmpeg-location') | ||
53 | options.push(process.env.FFMPEG_PATH) | ||
54 | } | ||
55 | |||
50 | return new Promise<string>(async (res, rej) => { | 56 | return new Promise<string>(async (res, rej) => { |
51 | const youtubeDL = await safeGetYoutubeDL() | 57 | const youtubeDL = await safeGetYoutubeDL() |
52 | youtubeDL.exec(url, options, processOptions, err => { | 58 | youtubeDL.exec(url, options, processOptions, err => { |
@@ -142,13 +148,33 @@ async function safeGetYoutubeDL () { | |||
142 | return youtubeDL | 148 | return youtubeDL |
143 | } | 149 | } |
144 | 150 | ||
151 | function buildOriginallyPublishedAt (obj: any) { | ||
152 | let originallyPublishedAt: Date = null | ||
153 | |||
154 | const uploadDateMatcher = /^(\d{4})(\d{2})(\d{2})$/.exec(obj.upload_date) | ||
155 | if (uploadDateMatcher) { | ||
156 | originallyPublishedAt = new Date() | ||
157 | originallyPublishedAt.setHours(0, 0, 0, 0) | ||
158 | |||
159 | const year = parseInt(uploadDateMatcher[1], 10) | ||
160 | // Month starts from 0 | ||
161 | const month = parseInt(uploadDateMatcher[2], 10) - 1 | ||
162 | const day = parseInt(uploadDateMatcher[3], 10) | ||
163 | |||
164 | originallyPublishedAt.setFullYear(year, month, day) | ||
165 | } | ||
166 | |||
167 | return originallyPublishedAt | ||
168 | } | ||
169 | |||
145 | // --------------------------------------------------------------------------- | 170 | // --------------------------------------------------------------------------- |
146 | 171 | ||
147 | export { | 172 | export { |
148 | updateYoutubeDLBinary, | 173 | updateYoutubeDLBinary, |
149 | downloadYoutubeDLVideo, | 174 | downloadYoutubeDLVideo, |
150 | getYoutubeDLInfo, | 175 | getYoutubeDLInfo, |
151 | safeGetYoutubeDL | 176 | safeGetYoutubeDL, |
177 | buildOriginallyPublishedAt | ||
152 | } | 178 | } |
153 | 179 | ||
154 | // --------------------------------------------------------------------------- | 180 | // --------------------------------------------------------------------------- |
@@ -180,7 +206,8 @@ function buildVideoInfo (obj: any) { | |||
180 | licence: getLicence(obj.license), | 206 | licence: getLicence(obj.license), |
181 | nsfw: isNSFW(obj), | 207 | nsfw: isNSFW(obj), |
182 | tags: getTags(obj.tags), | 208 | tags: getTags(obj.tags), |
183 | thumbnailUrl: obj.thumbnail || undefined | 209 | thumbnailUrl: obj.thumbnail || undefined, |
210 | originallyPublishedAt: buildOriginallyPublishedAt(obj) | ||
184 | } | 211 | } |
185 | } | 212 | } |
186 | 213 | ||
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts index 955d55206..db3115085 100644 --- a/server/initializers/checker-after-init.ts +++ b/server/initializers/checker-after-init.ts | |||
@@ -4,19 +4,20 @@ import { UserModel } from '../models/account/user' | |||
4 | import { ApplicationModel } from '../models/application/application' | 4 | import { ApplicationModel } from '../models/application/application' |
5 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 5 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
6 | import { parse } from 'url' | 6 | import { parse } from 'url' |
7 | import { CONFIG } from './constants' | 7 | import { CONFIG } from './config' |
8 | import { logger } from '../helpers/logger' | 8 | import { logger } from '../helpers/logger' |
9 | import { getServerActor } from '../helpers/utils' | 9 | import { getServerActor } from '../helpers/utils' |
10 | import { RecentlyAddedStrategy } from '../../shared/models/redundancy' | 10 | import { RecentlyAddedStrategy } from '../../shared/models/redundancy' |
11 | import { isArray } from '../helpers/custom-validators/misc' | 11 | import { isArray } from '../helpers/custom-validators/misc' |
12 | import { uniq } from 'lodash' | 12 | import { uniq } from 'lodash' |
13 | import { Emailer } from '../lib/emailer' | 13 | import { Emailer } from '../lib/emailer' |
14 | import { WEBSERVER } from './constants' | ||
14 | 15 | ||
15 | async function checkActivityPubUrls () { | 16 | async function checkActivityPubUrls () { |
16 | const actor = await getServerActor() | 17 | const actor = await getServerActor() |
17 | 18 | ||
18 | const parsed = parse(actor.url) | 19 | const parsed = parse(actor.url) |
19 | if (CONFIG.WEBSERVER.HOST !== parsed.host) { | 20 | if (WEBSERVER.HOST !== parsed.host) { |
20 | const NODE_ENV = config.util.getEnv('NODE_ENV') | 21 | const NODE_ENV = config.util.getEnv('NODE_ENV') |
21 | const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR') | 22 | const NODE_CONFIG_DIR = config.util.getEnv('NODE_CONFIG_DIR') |
22 | 23 | ||
@@ -34,6 +35,12 @@ async function checkActivityPubUrls () { | |||
34 | // Return an error message, or null if everything is okay | 35 | // Return an error message, or null if everything is okay |
35 | function checkConfig () { | 36 | function checkConfig () { |
36 | 37 | ||
38 | // Moved configuration keys | ||
39 | if (config.has('services.csp-logger')) { | ||
40 | logger.warn('services.csp-logger configuration has been renamed to csp.report_uri. Please update your configuration file.') | ||
41 | } | ||
42 | |||
43 | // Email verification | ||
37 | if (!Emailer.isEnabled()) { | 44 | if (!Emailer.isEnabled()) { |
38 | if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { | 45 | if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { |
39 | return 'Emailer is disabled but you require signup email verification.' | 46 | return 'Emailer is disabled but you require signup email verification.' |
@@ -77,6 +84,8 @@ function checkConfig () { | |||
77 | if (recentlyAddedStrategy && isNaN(recentlyAddedStrategy.minViews)) { | 84 | if (recentlyAddedStrategy && isNaN(recentlyAddedStrategy.minViews)) { |
78 | return 'Min views in recently added strategy is not a number' | 85 | return 'Min views in recently added strategy is not a number' |
79 | } | 86 | } |
87 | } else { | ||
88 | return 'Videos redundancy should be an array (you must uncomment lines containing - too)' | ||
80 | } | 89 | } |
81 | 90 | ||
82 | // Check storage directory locations | 91 | // Check storage directory locations |
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts index 7905d9ffa..622ad7d6b 100644 --- a/server/initializers/checker-before-init.ts +++ b/server/initializers/checker-before-init.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as config from 'config' | 1 | import * as config from 'config' |
2 | import { promisify0 } from '../helpers/core-utils' | 2 | import { promisify0 } from '../helpers/core-utils' |
3 | import { isArray } from '../helpers/custom-validators/misc' | ||
4 | 3 | ||
5 | // ONLY USE CORE MODULES IN THIS FILE! | 4 | // ONLY USE CORE MODULES IN THIS FILE! |
6 | 5 | ||
@@ -12,19 +11,24 @@ function checkMissedConfig () { | |||
12 | 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max', | 11 | 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', 'database.pool.max', |
13 | 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address', | 12 | 'smtp.hostname', 'smtp.port', 'smtp.username', 'smtp.password', 'smtp.tls', 'smtp.from_address', |
14 | 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache', | 13 | 'storage.avatars', 'storage.videos', 'storage.logs', 'storage.previews', 'storage.thumbnails', 'storage.torrents', 'storage.cache', |
15 | 'storage.redundancy', 'storage.tmp', | 14 | 'storage.redundancy', 'storage.tmp', 'storage.streaming_playlists', |
16 | 'log.level', | 15 | 'log.level', |
17 | 'user.video_quota', 'user.video_quota_daily', | 16 | 'user.video_quota', 'user.video_quota_daily', |
17 | 'csp.enabled', 'csp.report_only', 'csp.report_uri', | ||
18 | 'cache.previews.size', 'admin.email', 'contact_form.enabled', | 18 | 'cache.previews.size', 'admin.email', 'contact_form.enabled', |
19 | 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', | 19 | 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', |
20 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', | 20 | 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', |
21 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', | 21 | 'redundancy.videos.strategies', 'redundancy.videos.check_interval', |
22 | 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', | 22 | 'transcoding.enabled', 'transcoding.threads', 'transcoding.allow_additional_extensions', |
23 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', | 23 | 'import.videos.http.enabled', 'import.videos.torrent.enabled', 'auto_blacklist.videos.of_users.enabled', |
24 | 'trending.videos.interval_days', | 24 | 'trending.videos.interval_days', |
25 | 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', | 25 | 'instance.name', 'instance.short_description', 'instance.description', 'instance.terms', 'instance.default_client_route', |
26 | 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', | 26 | 'instance.is_nsfw', 'instance.default_nsfw_policy', 'instance.robots', 'instance.securitytxt', |
27 | 'services.twitter.username', 'services.twitter.whitelisted' | 27 | 'services.twitter.username', 'services.twitter.whitelisted', |
28 | 'followers.instance.enabled', 'followers.instance.manual_approval', | ||
29 | 'tracker.enabled', 'tracker.private', 'tracker.reject_too_many_announces', | ||
30 | 'history.videos.max_age', 'views.videos.remote.max_age', | ||
31 | 'rates_limit.login.window', 'rates_limit.login.max', 'rates_limit.ask_send_email.window', 'rates_limit.ask_send_email.max' | ||
28 | ] | 32 | ] |
29 | const requiredAlternatives = [ | 33 | const requiredAlternatives = [ |
30 | [ // set | 34 | [ // set |
@@ -41,7 +45,8 @@ function checkMissedConfig () { | |||
41 | } | 45 | } |
42 | 46 | ||
43 | const redundancyVideos = config.get<any>('redundancy.videos.strategies') | 47 | const redundancyVideos = config.get<any>('redundancy.videos.strategies') |
44 | if (isArray(redundancyVideos)) { | 48 | |
49 | if (Array.isArray(redundancyVideos)) { | ||
45 | for (const r of redundancyVideos) { | 50 | for (const r of redundancyVideos) { |
46 | if (!r.size) miss.push('redundancy.videos.strategies.size') | 51 | if (!r.size) miss.push('redundancy.videos.strategies.size') |
47 | if (!r.min_lifetime) miss.push('redundancy.videos.strategies.min_lifetime') | 52 | if (!r.min_lifetime) miss.push('redundancy.videos.strategies.min_lifetime') |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts new file mode 100644 index 000000000..4f77e144d --- /dev/null +++ b/server/initializers/config.ts | |||
@@ -0,0 +1,277 @@ | |||
1 | import { IConfig } from 'config' | ||
2 | import { dirname, join } from 'path' | ||
3 | import { VideosRedundancy } from '../../shared/models' | ||
4 | // Do not use barrels, remain constants as independent as possible | ||
5 | import { buildPath, parseBytes, parseDurationToMs, root } from '../helpers/core-utils' | ||
6 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | ||
7 | import * as bytes from 'bytes' | ||
8 | |||
9 | // Use a variable to reload the configuration if we need | ||
10 | let config: IConfig = require('config') | ||
11 | |||
12 | const configChangedHandlers: Function[] = [] | ||
13 | |||
14 | const CONFIG = { | ||
15 | CUSTOM_FILE: getLocalConfigFilePath(), | ||
16 | LISTEN: { | ||
17 | PORT: config.get<number>('listen.port'), | ||
18 | HOSTNAME: config.get<string>('listen.hostname') | ||
19 | }, | ||
20 | DATABASE: { | ||
21 | DBNAME: 'peertube' + config.get<string>('database.suffix'), | ||
22 | HOSTNAME: config.get<string>('database.hostname'), | ||
23 | PORT: config.get<number>('database.port'), | ||
24 | USERNAME: config.get<string>('database.username'), | ||
25 | PASSWORD: config.get<string>('database.password'), | ||
26 | POOL: { | ||
27 | MAX: config.get<number>('database.pool.max') | ||
28 | } | ||
29 | }, | ||
30 | REDIS: { | ||
31 | HOSTNAME: config.has('redis.hostname') ? config.get<string>('redis.hostname') : null, | ||
32 | PORT: config.has('redis.port') ? config.get<number>('redis.port') : null, | ||
33 | SOCKET: config.has('redis.socket') ? config.get<string>('redis.socket') : null, | ||
34 | AUTH: config.has('redis.auth') ? config.get<string>('redis.auth') : null, | ||
35 | DB: config.has('redis.db') ? config.get<number>('redis.db') : null | ||
36 | }, | ||
37 | SMTP: { | ||
38 | HOSTNAME: config.get<string>('smtp.hostname'), | ||
39 | PORT: config.get<number>('smtp.port'), | ||
40 | USERNAME: config.get<string>('smtp.username'), | ||
41 | PASSWORD: config.get<string>('smtp.password'), | ||
42 | TLS: config.get<boolean>('smtp.tls'), | ||
43 | DISABLE_STARTTLS: config.get<boolean>('smtp.disable_starttls'), | ||
44 | CA_FILE: config.get<string>('smtp.ca_file'), | ||
45 | FROM_ADDRESS: config.get<string>('smtp.from_address') | ||
46 | }, | ||
47 | STORAGE: { | ||
48 | TMP_DIR: buildPath(config.get<string>('storage.tmp')), | ||
49 | AVATARS_DIR: buildPath(config.get<string>('storage.avatars')), | ||
50 | LOG_DIR: buildPath(config.get<string>('storage.logs')), | ||
51 | VIDEOS_DIR: buildPath(config.get<string>('storage.videos')), | ||
52 | STREAMING_PLAYLISTS_DIR: buildPath(config.get<string>('storage.streaming_playlists')), | ||
53 | REDUNDANCY_DIR: buildPath(config.get<string>('storage.redundancy')), | ||
54 | THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')), | ||
55 | PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')), | ||
56 | CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')), | ||
57 | TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')), | ||
58 | CACHE_DIR: buildPath(config.get<string>('storage.cache')) | ||
59 | }, | ||
60 | WEBSERVER: { | ||
61 | SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http', | ||
62 | WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws', | ||
63 | HOSTNAME: config.get<string>('webserver.hostname'), | ||
64 | PORT: config.get<number>('webserver.port') | ||
65 | }, | ||
66 | RATES_LIMIT: { | ||
67 | LOGIN: { | ||
68 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.login.window')), | ||
69 | MAX: config.get<number>('rates_limit.login.max') | ||
70 | }, | ||
71 | ASK_SEND_EMAIL: { | ||
72 | WINDOW_MS: parseDurationToMs(config.get<string>('rates_limit.ask_send_email.window')), | ||
73 | MAX: config.get<number>('rates_limit.ask_send_email.max') | ||
74 | } | ||
75 | }, | ||
76 | TRUST_PROXY: config.get<string[]>('trust_proxy'), | ||
77 | LOG: { | ||
78 | LEVEL: config.get<string>('log.level') | ||
79 | }, | ||
80 | SEARCH: { | ||
81 | REMOTE_URI: { | ||
82 | USERS: config.get<boolean>('search.remote_uri.users'), | ||
83 | ANONYMOUS: config.get<boolean>('search.remote_uri.anonymous') | ||
84 | } | ||
85 | }, | ||
86 | TRENDING: { | ||
87 | VIDEOS: { | ||
88 | INTERVAL_DAYS: config.get<number>('trending.videos.interval_days') | ||
89 | } | ||
90 | }, | ||
91 | REDUNDANCY: { | ||
92 | VIDEOS: { | ||
93 | CHECK_INTERVAL: parseDurationToMs(config.get<string>('redundancy.videos.check_interval')), | ||
94 | STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies')) | ||
95 | } | ||
96 | }, | ||
97 | CSP: { | ||
98 | ENABLED: config.get<boolean>('csp.enabled'), | ||
99 | REPORT_ONLY: config.get<boolean>('csp.report_only'), | ||
100 | REPORT_URI: config.get<boolean>('csp.report_uri') | ||
101 | }, | ||
102 | TRACKER: { | ||
103 | ENABLED: config.get<boolean>('tracker.enabled'), | ||
104 | PRIVATE: config.get<boolean>('tracker.private'), | ||
105 | REJECT_TOO_MANY_ANNOUNCES: config.get<boolean>('tracker.reject_too_many_announces') | ||
106 | }, | ||
107 | HISTORY: { | ||
108 | VIDEOS: { | ||
109 | MAX_AGE: parseDurationToMs(config.get('history.videos.max_age')) | ||
110 | } | ||
111 | }, | ||
112 | VIEWS: { | ||
113 | VIDEOS: { | ||
114 | REMOTE: { | ||
115 | MAX_AGE: parseDurationToMs(config.get('views.videos.remote.max_age')) | ||
116 | } | ||
117 | } | ||
118 | }, | ||
119 | ADMIN: { | ||
120 | get EMAIL () { return config.get<string>('admin.email') } | ||
121 | }, | ||
122 | CONTACT_FORM: { | ||
123 | get ENABLED () { return config.get<boolean>('contact_form.enabled') } | ||
124 | }, | ||
125 | SIGNUP: { | ||
126 | get ENABLED () { return config.get<boolean>('signup.enabled') }, | ||
127 | get LIMIT () { return config.get<number>('signup.limit') }, | ||
128 | get REQUIRES_EMAIL_VERIFICATION () { return config.get<boolean>('signup.requires_email_verification') }, | ||
129 | FILTERS: { | ||
130 | CIDR: { | ||
131 | get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') }, | ||
132 | get BLACKLIST () { return config.get<string[]>('signup.filters.cidr.blacklist') } | ||
133 | } | ||
134 | } | ||
135 | }, | ||
136 | USER: { | ||
137 | get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) }, | ||
138 | get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) } | ||
139 | }, | ||
140 | TRANSCODING: { | ||
141 | get ENABLED () { return config.get<boolean>('transcoding.enabled') }, | ||
142 | get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') }, | ||
143 | get THREADS () { return config.get<number>('transcoding.threads') }, | ||
144 | RESOLUTIONS: { | ||
145 | get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') }, | ||
146 | get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') }, | ||
147 | get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') }, | ||
148 | get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') }, | ||
149 | get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') } | ||
150 | }, | ||
151 | HLS: { | ||
152 | get ENABLED () { return config.get<boolean>('transcoding.hls.enabled') } | ||
153 | } | ||
154 | }, | ||
155 | IMPORT: { | ||
156 | VIDEOS: { | ||
157 | HTTP: { | ||
158 | get ENABLED () { return config.get<boolean>('import.videos.http.enabled') } | ||
159 | }, | ||
160 | TORRENT: { | ||
161 | get ENABLED () { return config.get<boolean>('import.videos.torrent.enabled') } | ||
162 | } | ||
163 | } | ||
164 | }, | ||
165 | AUTO_BLACKLIST: { | ||
166 | VIDEOS: { | ||
167 | OF_USERS: { | ||
168 | get ENABLED () { return config.get<boolean>('auto_blacklist.videos.of_users.enabled') } | ||
169 | } | ||
170 | } | ||
171 | }, | ||
172 | CACHE: { | ||
173 | PREVIEWS: { | ||
174 | get SIZE () { return config.get<number>('cache.previews.size') } | ||
175 | }, | ||
176 | VIDEO_CAPTIONS: { | ||
177 | get SIZE () { return config.get<number>('cache.captions.size') } | ||
178 | } | ||
179 | }, | ||
180 | INSTANCE: { | ||
181 | get NAME () { return config.get<string>('instance.name') }, | ||
182 | get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') }, | ||
183 | get DESCRIPTION () { return config.get<string>('instance.description') }, | ||
184 | get TERMS () { return config.get<string>('instance.terms') }, | ||
185 | get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') }, | ||
186 | get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, | ||
187 | get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') }, | ||
188 | CUSTOMIZATIONS: { | ||
189 | get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') }, | ||
190 | get CSS () { return config.get<string>('instance.customizations.css') } | ||
191 | }, | ||
192 | get ROBOTS () { return config.get<string>('instance.robots') }, | ||
193 | get SECURITYTXT () { return config.get<string>('instance.securitytxt') }, | ||
194 | get SECURITYTXT_CONTACT () { return config.get<string>('admin.email') } | ||
195 | }, | ||
196 | SERVICES: { | ||
197 | TWITTER: { | ||
198 | get USERNAME () { return config.get<string>('services.twitter.username') }, | ||
199 | get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') } | ||
200 | } | ||
201 | }, | ||
202 | FOLLOWERS: { | ||
203 | INSTANCE: { | ||
204 | get ENABLED () { return config.get<boolean>('followers.instance.enabled') }, | ||
205 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } | ||
206 | } | ||
207 | } | ||
208 | } | ||
209 | |||
210 | function registerConfigChangedHandler (fun: Function) { | ||
211 | configChangedHandlers.push(fun) | ||
212 | } | ||
213 | |||
214 | // --------------------------------------------------------------------------- | ||
215 | |||
216 | export { | ||
217 | CONFIG, | ||
218 | registerConfigChangedHandler | ||
219 | } | ||
220 | |||
221 | // --------------------------------------------------------------------------- | ||
222 | |||
223 | function getLocalConfigFilePath () { | ||
224 | const configSources = config.util.getConfigSources() | ||
225 | if (configSources.length === 0) throw new Error('Invalid config source.') | ||
226 | |||
227 | let filename = 'local' | ||
228 | if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}` | ||
229 | if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}` | ||
230 | |||
231 | return join(dirname(configSources[ 0 ].name), filename + '.json') | ||
232 | } | ||
233 | |||
234 | function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { | ||
235 | if (!objs) return [] | ||
236 | |||
237 | if (!Array.isArray(objs)) return objs | ||
238 | |||
239 | return objs.map(obj => { | ||
240 | return Object.assign({}, obj, { | ||
241 | minLifetime: parseDurationToMs(obj.min_lifetime), | ||
242 | size: bytes.parse(obj.size), | ||
243 | minViews: obj.min_views | ||
244 | }) | ||
245 | }) | ||
246 | } | ||
247 | |||
248 | export function reloadConfig () { | ||
249 | |||
250 | function directory () { | ||
251 | if (process.env.NODE_CONFIG_DIR) { | ||
252 | return process.env.NODE_CONFIG_DIR | ||
253 | } | ||
254 | |||
255 | return join(root(), 'config') | ||
256 | } | ||
257 | |||
258 | function purge () { | ||
259 | for (const fileName in require.cache) { | ||
260 | if (-1 === fileName.indexOf(directory())) { | ||
261 | continue | ||
262 | } | ||
263 | |||
264 | delete require.cache[fileName] | ||
265 | } | ||
266 | |||
267 | delete require.cache[require.resolve('config')] | ||
268 | } | ||
269 | |||
270 | purge() | ||
271 | |||
272 | config = require('config') | ||
273 | |||
274 | for (const configChangedHandler of configChangedHandlers) { | ||
275 | configChangedHandler() | ||
276 | } | ||
277 | } | ||
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 6f3ebb9aa..62778ae58 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -1,22 +1,20 @@ | |||
1 | import { IConfig } from 'config' | 1 | import { join } from 'path' |
2 | import { dirname, join } from 'path' | 2 | import { JobType, VideoRateType, VideoState } from '../../shared/models' |
3 | import { JobType, VideoRateType, VideoState, VideosRedundancy } from '../../shared/models' | ||
4 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 3 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
5 | import { FollowState } from '../../shared/models/actors' | 4 | import { FollowState } from '../../shared/models/actors' |
6 | import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' | 5 | import { VideoAbuseState, VideoImportState, VideoPrivacy, VideoTranscodingFPS } from '../../shared/models/videos' |
7 | // Do not use barrels, remain constants as independent as possible | 6 | // Do not use barrels, remain constants as independent as possible |
8 | import { buildPath, isTestInstance, parseDuration, parseBytes, root, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' | 7 | import { isTestInstance, sanitizeHost, sanitizeUrl } from '../helpers/core-utils' |
9 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' | 8 | import { NSFWPolicyType } from '../../shared/models/videos/nsfw-policy.type' |
10 | import { invert } from 'lodash' | 9 | import { invert } from 'lodash' |
11 | import { CronRepeatOptions, EveryRepeatOptions } from 'bull' | 10 | import { CronRepeatOptions, EveryRepeatOptions } from 'bull' |
12 | import * as bytes from 'bytes' | 11 | import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' |
13 | 12 | import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' | |
14 | // Use a variable to reload the configuration if we need | 13 | import { CONFIG, registerConfigChangedHandler } from './config' |
15 | let config: IConfig = require('config') | ||
16 | 14 | ||
17 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
18 | 16 | ||
19 | const LAST_MIGRATION_VERSION = 325 | 17 | const LAST_MIGRATION_VERSION = 375 |
20 | 18 | ||
21 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
22 | 20 | ||
@@ -30,6 +28,15 @@ const PAGINATION = { | |||
30 | } | 28 | } |
31 | } | 29 | } |
32 | 30 | ||
31 | const WEBSERVER = { | ||
32 | URL: '', | ||
33 | HOST: '', | ||
34 | SCHEME: '', | ||
35 | WS: '', | ||
36 | HOSTNAME: '', | ||
37 | PORT: 0 | ||
38 | } | ||
39 | |||
33 | // Sortable columns per schema | 40 | // Sortable columns per schema |
34 | const SORTABLE_COLUMNS = { | 41 | const SORTABLE_COLUMNS = { |
35 | USERS: [ 'id', 'username', 'createdAt' ], | 42 | USERS: [ 'id', 'username', 'createdAt' ], |
@@ -40,6 +47,7 @@ const SORTABLE_COLUMNS = { | |||
40 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], | 47 | VIDEO_CHANNELS: [ 'id', 'name', 'updatedAt', 'createdAt' ], |
41 | VIDEO_IMPORTS: [ 'createdAt' ], | 48 | VIDEO_IMPORTS: [ 'createdAt' ], |
42 | VIDEO_COMMENT_THREADS: [ 'createdAt' ], | 49 | VIDEO_COMMENT_THREADS: [ 'createdAt' ], |
50 | VIDEO_RATES: [ 'createdAt' ], | ||
43 | BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], | 51 | BLACKLISTS: [ 'id', 'name', 'duration', 'views', 'likes', 'dislikes', 'uuid', 'createdAt' ], |
44 | FOLLOWERS: [ 'createdAt' ], | 52 | FOLLOWERS: [ 'createdAt' ], |
45 | FOLLOWING: [ 'createdAt' ], | 53 | FOLLOWING: [ 'createdAt' ], |
@@ -52,7 +60,9 @@ const SORTABLE_COLUMNS = { | |||
52 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], | 60 | ACCOUNTS_BLOCKLIST: [ 'createdAt' ], |
53 | SERVERS_BLOCKLIST: [ 'createdAt' ], | 61 | SERVERS_BLOCKLIST: [ 'createdAt' ], |
54 | 62 | ||
55 | USER_NOTIFICATIONS: [ 'createdAt' ] | 63 | USER_NOTIFICATIONS: [ 'createdAt' ], |
64 | |||
65 | VIDEO_PLAYLISTS: [ 'displayName', 'createdAt', 'updatedAt' ] | ||
56 | } | 66 | } |
57 | 67 | ||
58 | const OAUTH_LIFETIME = { | 68 | const OAUTH_LIFETIME = { |
@@ -96,36 +106,40 @@ const REMOTE_SCHEME = { | |||
96 | WS: 'wss' | 106 | WS: 'wss' |
97 | } | 107 | } |
98 | 108 | ||
99 | const JOB_ATTEMPTS: { [ id in JobType ]: number } = { | 109 | // TODO: remove 'video-file' |
110 | const JOB_ATTEMPTS: { [id in (JobType | 'video-file')]: number } = { | ||
100 | 'activitypub-http-broadcast': 5, | 111 | 'activitypub-http-broadcast': 5, |
101 | 'activitypub-http-unicast': 5, | 112 | 'activitypub-http-unicast': 5, |
102 | 'activitypub-http-fetcher': 5, | 113 | 'activitypub-http-fetcher': 5, |
103 | 'activitypub-follow': 5, | 114 | 'activitypub-follow': 5, |
104 | 'video-file-import': 1, | 115 | 'video-file-import': 1, |
116 | 'video-transcoding': 1, | ||
105 | 'video-file': 1, | 117 | 'video-file': 1, |
106 | 'video-import': 1, | 118 | 'video-import': 1, |
107 | 'email': 5, | 119 | 'email': 5, |
108 | 'videos-views': 1, | 120 | 'videos-views': 1, |
109 | 'activitypub-refresher': 1 | 121 | 'activitypub-refresher': 1 |
110 | } | 122 | } |
111 | const JOB_CONCURRENCY: { [ id in JobType ]: number } = { | 123 | const JOB_CONCURRENCY: { [id in (JobType | 'video-file')]: number } = { |
112 | 'activitypub-http-broadcast': 1, | 124 | 'activitypub-http-broadcast': 1, |
113 | 'activitypub-http-unicast': 5, | 125 | 'activitypub-http-unicast': 5, |
114 | 'activitypub-http-fetcher': 1, | 126 | 'activitypub-http-fetcher': 1, |
115 | 'activitypub-follow': 3, | 127 | 'activitypub-follow': 3, |
116 | 'video-file-import': 1, | 128 | 'video-file-import': 1, |
129 | 'video-transcoding': 1, | ||
117 | 'video-file': 1, | 130 | 'video-file': 1, |
118 | 'video-import': 1, | 131 | 'video-import': 1, |
119 | 'email': 5, | 132 | 'email': 5, |
120 | 'videos-views': 1, | 133 | 'videos-views': 1, |
121 | 'activitypub-refresher': 1 | 134 | 'activitypub-refresher': 1 |
122 | } | 135 | } |
123 | const JOB_TTL: { [ id in JobType ]: number } = { | 136 | const JOB_TTL: { [id in (JobType | 'video-file')]: number } = { |
124 | 'activitypub-http-broadcast': 60000 * 10, // 10 minutes | 137 | 'activitypub-http-broadcast': 60000 * 10, // 10 minutes |
125 | 'activitypub-http-unicast': 60000 * 10, // 10 minutes | 138 | 'activitypub-http-unicast': 60000 * 10, // 10 minutes |
126 | 'activitypub-http-fetcher': 60000 * 10, // 10 minutes | 139 | 'activitypub-http-fetcher': 60000 * 10, // 10 minutes |
127 | 'activitypub-follow': 60000 * 10, // 10 minutes | 140 | 'activitypub-follow': 60000 * 10, // 10 minutes |
128 | 'video-file-import': 1000 * 3600, // 1 hour | 141 | 'video-file-import': 1000 * 3600, // 1 hour |
142 | 'video-transcoding': 1000 * 3600 * 48, // 2 days, transcoding could be long | ||
129 | 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long | 143 | 'video-file': 1000 * 3600 * 48, // 2 days, transcoding could be long |
130 | 'video-import': 1000 * 3600 * 2, // hours | 144 | 'video-import': 1000 * 3600 * 2, // hours |
131 | 'email': 60000 * 10, // 10 minutes | 145 | 'email': 60000 * 10, // 10 minutes |
@@ -144,163 +158,13 @@ const JOB_REQUEST_TIMEOUT = 3000 // 3 seconds | |||
144 | const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days | 158 | const JOB_COMPLETED_LIFETIME = 60000 * 60 * 24 * 2 // 2 days |
145 | const VIDEO_IMPORT_TIMEOUT = 1000 * 3600 // 1 hour | 159 | const VIDEO_IMPORT_TIMEOUT = 1000 * 3600 // 1 hour |
146 | 160 | ||
147 | // 1 hour | 161 | const SCHEDULER_INTERVALS_MS = { |
148 | let SCHEDULER_INTERVALS_MS = { | ||
149 | actorFollowScores: 60000 * 60, // 1 hour | 162 | actorFollowScores: 60000 * 60, // 1 hour |
150 | removeOldJobs: 60000 * 60, // 1 hour | 163 | removeOldJobs: 60000 * 60, // 1 hour |
151 | updateVideos: 60000, // 1 minute | 164 | updateVideos: 60000, // 1 minute |
152 | youtubeDLUpdate: 60000 * 60 * 24 // 1 day | 165 | youtubeDLUpdate: 60000 * 60 * 24, // 1 day |
153 | } | 166 | removeOldViews: 60000 * 60 * 24, // 1 day |
154 | 167 | removeOldHistory: 60000 * 60 * 24 // 1 day | |
155 | // --------------------------------------------------------------------------- | ||
156 | |||
157 | const CONFIG = { | ||
158 | CUSTOM_FILE: getLocalConfigFilePath(), | ||
159 | LISTEN: { | ||
160 | PORT: config.get<number>('listen.port'), | ||
161 | HOSTNAME: config.get<string>('listen.hostname') | ||
162 | }, | ||
163 | DATABASE: { | ||
164 | DBNAME: 'peertube' + config.get<string>('database.suffix'), | ||
165 | HOSTNAME: config.get<string>('database.hostname'), | ||
166 | PORT: config.get<number>('database.port'), | ||
167 | USERNAME: config.get<string>('database.username'), | ||
168 | PASSWORD: config.get<string>('database.password'), | ||
169 | POOL: { | ||
170 | MAX: config.get<number>('database.pool.max') | ||
171 | } | ||
172 | }, | ||
173 | REDIS: { | ||
174 | HOSTNAME: config.has('redis.hostname') ? config.get<string>('redis.hostname') : null, | ||
175 | PORT: config.has('redis.port') ? config.get<number>('redis.port') : null, | ||
176 | SOCKET: config.has('redis.socket') ? config.get<string>('redis.socket') : null, | ||
177 | AUTH: config.has('redis.auth') ? config.get<string>('redis.auth') : null, | ||
178 | DB: config.has('redis.db') ? config.get<number>('redis.db') : null | ||
179 | }, | ||
180 | SMTP: { | ||
181 | HOSTNAME: config.get<string>('smtp.hostname'), | ||
182 | PORT: config.get<number>('smtp.port'), | ||
183 | USERNAME: config.get<string>('smtp.username'), | ||
184 | PASSWORD: config.get<string>('smtp.password'), | ||
185 | TLS: config.get<boolean>('smtp.tls'), | ||
186 | DISABLE_STARTTLS: config.get<boolean>('smtp.disable_starttls'), | ||
187 | CA_FILE: config.get<string>('smtp.ca_file'), | ||
188 | FROM_ADDRESS: config.get<string>('smtp.from_address') | ||
189 | }, | ||
190 | STORAGE: { | ||
191 | TMP_DIR: buildPath(config.get<string>('storage.tmp')), | ||
192 | AVATARS_DIR: buildPath(config.get<string>('storage.avatars')), | ||
193 | LOG_DIR: buildPath(config.get<string>('storage.logs')), | ||
194 | VIDEOS_DIR: buildPath(config.get<string>('storage.videos')), | ||
195 | REDUNDANCY_DIR: buildPath(config.get<string>('storage.redundancy')), | ||
196 | THUMBNAILS_DIR: buildPath(config.get<string>('storage.thumbnails')), | ||
197 | PREVIEWS_DIR: buildPath(config.get<string>('storage.previews')), | ||
198 | CAPTIONS_DIR: buildPath(config.get<string>('storage.captions')), | ||
199 | TORRENTS_DIR: buildPath(config.get<string>('storage.torrents')), | ||
200 | CACHE_DIR: buildPath(config.get<string>('storage.cache')) | ||
201 | }, | ||
202 | WEBSERVER: { | ||
203 | SCHEME: config.get<boolean>('webserver.https') === true ? 'https' : 'http', | ||
204 | WS: config.get<boolean>('webserver.https') === true ? 'wss' : 'ws', | ||
205 | HOSTNAME: config.get<string>('webserver.hostname'), | ||
206 | PORT: config.get<number>('webserver.port'), | ||
207 | URL: '', | ||
208 | HOST: '' | ||
209 | }, | ||
210 | TRUST_PROXY: config.get<string[]>('trust_proxy'), | ||
211 | LOG: { | ||
212 | LEVEL: config.get<string>('log.level') | ||
213 | }, | ||
214 | SEARCH: { | ||
215 | REMOTE_URI: { | ||
216 | USERS: config.get<boolean>('search.remote_uri.users'), | ||
217 | ANONYMOUS: config.get<boolean>('search.remote_uri.anonymous') | ||
218 | } | ||
219 | }, | ||
220 | TRENDING: { | ||
221 | VIDEOS: { | ||
222 | INTERVAL_DAYS: config.get<number>('trending.videos.interval_days') | ||
223 | } | ||
224 | }, | ||
225 | REDUNDANCY: { | ||
226 | VIDEOS: { | ||
227 | CHECK_INTERVAL: parseDuration(config.get<string>('redundancy.videos.check_interval')), | ||
228 | STRATEGIES: buildVideosRedundancy(config.get<any[]>('redundancy.videos.strategies')) | ||
229 | } | ||
230 | }, | ||
231 | ADMIN: { | ||
232 | get EMAIL () { return config.get<string>('admin.email') } | ||
233 | }, | ||
234 | CONTACT_FORM: { | ||
235 | get ENABLED () { return config.get<boolean>('contact_form.enabled') } | ||
236 | }, | ||
237 | SIGNUP: { | ||
238 | get ENABLED () { return config.get<boolean>('signup.enabled') }, | ||
239 | get LIMIT () { return config.get<number>('signup.limit') }, | ||
240 | get REQUIRES_EMAIL_VERIFICATION () { return config.get<boolean>('signup.requires_email_verification') }, | ||
241 | FILTERS: { | ||
242 | CIDR: { | ||
243 | get WHITELIST () { return config.get<string[]>('signup.filters.cidr.whitelist') }, | ||
244 | get BLACKLIST () { return config.get<string[]>('signup.filters.cidr.blacklist') } | ||
245 | } | ||
246 | } | ||
247 | }, | ||
248 | USER: { | ||
249 | get VIDEO_QUOTA () { return parseBytes(config.get<number>('user.video_quota')) }, | ||
250 | get VIDEO_QUOTA_DAILY () { return parseBytes(config.get<number>('user.video_quota_daily')) } | ||
251 | }, | ||
252 | TRANSCODING: { | ||
253 | get ENABLED () { return config.get<boolean>('transcoding.enabled') }, | ||
254 | get ALLOW_ADDITIONAL_EXTENSIONS () { return config.get<boolean>('transcoding.allow_additional_extensions') }, | ||
255 | get THREADS () { return config.get<number>('transcoding.threads') }, | ||
256 | RESOLUTIONS: { | ||
257 | get '240p' () { return config.get<boolean>('transcoding.resolutions.240p') }, | ||
258 | get '360p' () { return config.get<boolean>('transcoding.resolutions.360p') }, | ||
259 | get '480p' () { return config.get<boolean>('transcoding.resolutions.480p') }, | ||
260 | get '720p' () { return config.get<boolean>('transcoding.resolutions.720p') }, | ||
261 | get '1080p' () { return config.get<boolean>('transcoding.resolutions.1080p') } | ||
262 | } | ||
263 | }, | ||
264 | IMPORT: { | ||
265 | VIDEOS: { | ||
266 | HTTP: { | ||
267 | get ENABLED () { return config.get<boolean>('import.videos.http.enabled') } | ||
268 | }, | ||
269 | TORRENT: { | ||
270 | get ENABLED () { return config.get<boolean>('import.videos.torrent.enabled') } | ||
271 | } | ||
272 | } | ||
273 | }, | ||
274 | CACHE: { | ||
275 | PREVIEWS: { | ||
276 | get SIZE () { return config.get<number>('cache.previews.size') } | ||
277 | }, | ||
278 | VIDEO_CAPTIONS: { | ||
279 | get SIZE () { return config.get<number>('cache.captions.size') } | ||
280 | } | ||
281 | }, | ||
282 | INSTANCE: { | ||
283 | get NAME () { return config.get<string>('instance.name') }, | ||
284 | get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') }, | ||
285 | get DESCRIPTION () { return config.get<string>('instance.description') }, | ||
286 | get TERMS () { return config.get<string>('instance.terms') }, | ||
287 | get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, | ||
288 | get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') }, | ||
289 | CUSTOMIZATIONS: { | ||
290 | get JAVASCRIPT () { return config.get<string>('instance.customizations.javascript') }, | ||
291 | get CSS () { return config.get<string>('instance.customizations.css') } | ||
292 | }, | ||
293 | get ROBOTS () { return config.get<string>('instance.robots') }, | ||
294 | get SECURITYTXT () { return config.get<string>('instance.securitytxt') }, | ||
295 | get SECURITYTXT_CONTACT () { return config.get<string>('admin.email') } | ||
296 | }, | ||
297 | SERVICES: { | ||
298 | get 'CSP-LOGGER' () { return config.get<string>('services.csp-logger') }, | ||
299 | TWITTER: { | ||
300 | get USERNAME () { return config.get<string>('services.twitter.username') }, | ||
301 | get WHITELISTED () { return config.get<boolean>('services.twitter.whitelisted') } | ||
302 | } | ||
303 | } | ||
304 | } | 168 | } |
305 | 169 | ||
306 | // --------------------------------------------------------------------------- | 170 | // --------------------------------------------------------------------------- |
@@ -377,6 +241,17 @@ let CONSTRAINTS_FIELDS = { | |||
377 | FILE_SIZE: { min: 10 }, | 241 | FILE_SIZE: { min: 10 }, |
378 | URL: { min: 3, max: 2000 } // Length | 242 | URL: { min: 3, max: 2000 } // Length |
379 | }, | 243 | }, |
244 | VIDEO_PLAYLISTS: { | ||
245 | NAME: { min: 1, max: 120 }, // Length | ||
246 | DESCRIPTION: { min: 3, max: 1000 }, // Length | ||
247 | URL: { min: 3, max: 2000 }, // Length | ||
248 | IMAGE: { | ||
249 | EXTNAME: [ '.jpg', '.jpeg' ], | ||
250 | FILE_SIZE: { | ||
251 | max: 2 * 1024 * 1024 // 2MB | ||
252 | } | ||
253 | } | ||
254 | }, | ||
380 | ACTORS: { | 255 | ACTORS: { |
381 | PUBLIC_KEY: { min: 10, max: 5000 }, // Length | 256 | PUBLIC_KEY: { min: 10, max: 5000 }, // Length |
382 | PRIVATE_KEY: { min: 10, max: 5000 }, // Length | 257 | PRIVATE_KEY: { min: 10, max: 5000 }, // Length |
@@ -406,12 +281,12 @@ let CONSTRAINTS_FIELDS = { | |||
406 | 281 | ||
407 | const RATES_LIMIT = { | 282 | const RATES_LIMIT = { |
408 | LOGIN: { | 283 | LOGIN: { |
409 | WINDOW_MS: 5 * 60 * 1000, // 5 minutes | 284 | WINDOW_MS: CONFIG.RATES_LIMIT.LOGIN.WINDOW_MS, |
410 | MAX: 15 // 15 attempts | 285 | MAX: CONFIG.RATES_LIMIT.LOGIN.MAX |
411 | }, | 286 | }, |
412 | ASK_SEND_EMAIL: { | 287 | ASK_SEND_EMAIL: { |
413 | WINDOW_MS: 5 * 60 * 1000, // 5 minutes | 288 | WINDOW_MS: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.WINDOW_MS, |
414 | MAX: 3 // 3 attempts | 289 | MAX: CONFIG.RATES_LIMIT.ASK_SEND_EMAIL.MAX |
415 | } | 290 | } |
416 | } | 291 | } |
417 | 292 | ||
@@ -467,30 +342,41 @@ const VIDEO_LICENCES = { | |||
467 | 7: 'Public Domain Dedication' | 342 | 7: 'Public Domain Dedication' |
468 | } | 343 | } |
469 | 344 | ||
470 | const VIDEO_LANGUAGES = buildLanguages() | 345 | let VIDEO_LANGUAGES: { [id: string]: string } = {} |
471 | 346 | ||
472 | const VIDEO_PRIVACIES = { | 347 | const VIDEO_PRIVACIES = { |
473 | [VideoPrivacy.PUBLIC]: 'Public', | 348 | [ VideoPrivacy.PUBLIC ]: 'Public', |
474 | [VideoPrivacy.UNLISTED]: 'Unlisted', | 349 | [ VideoPrivacy.UNLISTED ]: 'Unlisted', |
475 | [VideoPrivacy.PRIVATE]: 'Private' | 350 | [ VideoPrivacy.PRIVATE ]: 'Private' |
476 | } | 351 | } |
477 | 352 | ||
478 | const VIDEO_STATES = { | 353 | const VIDEO_STATES = { |
479 | [VideoState.PUBLISHED]: 'Published', | 354 | [ VideoState.PUBLISHED ]: 'Published', |
480 | [VideoState.TO_TRANSCODE]: 'To transcode', | 355 | [ VideoState.TO_TRANSCODE ]: 'To transcode', |
481 | [VideoState.TO_IMPORT]: 'To import' | 356 | [ VideoState.TO_IMPORT ]: 'To import' |
482 | } | 357 | } |
483 | 358 | ||
484 | const VIDEO_IMPORT_STATES = { | 359 | const VIDEO_IMPORT_STATES = { |
485 | [VideoImportState.FAILED]: 'Failed', | 360 | [ VideoImportState.FAILED ]: 'Failed', |
486 | [VideoImportState.PENDING]: 'Pending', | 361 | [ VideoImportState.PENDING ]: 'Pending', |
487 | [VideoImportState.SUCCESS]: 'Success' | 362 | [ VideoImportState.SUCCESS ]: 'Success' |
488 | } | 363 | } |
489 | 364 | ||
490 | const VIDEO_ABUSE_STATES = { | 365 | const VIDEO_ABUSE_STATES = { |
491 | [VideoAbuseState.PENDING]: 'Pending', | 366 | [ VideoAbuseState.PENDING ]: 'Pending', |
492 | [VideoAbuseState.REJECTED]: 'Rejected', | 367 | [ VideoAbuseState.REJECTED ]: 'Rejected', |
493 | [VideoAbuseState.ACCEPTED]: 'Accepted' | 368 | [ VideoAbuseState.ACCEPTED ]: 'Accepted' |
369 | } | ||
370 | |||
371 | const VIDEO_PLAYLIST_PRIVACIES = { | ||
372 | [ VideoPlaylistPrivacy.PUBLIC ]: 'Public', | ||
373 | [ VideoPlaylistPrivacy.UNLISTED ]: 'Unlisted', | ||
374 | [ VideoPlaylistPrivacy.PRIVATE ]: 'Private' | ||
375 | } | ||
376 | |||
377 | const VIDEO_PLAYLIST_TYPES = { | ||
378 | [ VideoPlaylistType.REGULAR ]: 'Regular', | ||
379 | [ VideoPlaylistType.WATCH_LATER ]: 'Watch later' | ||
494 | } | 380 | } |
495 | 381 | ||
496 | const MIMETYPES = { | 382 | const MIMETYPES = { |
@@ -548,8 +434,9 @@ const ACTIVITY_PUB = { | |||
548 | MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ] | 434 | MAGNET: [ 'application/x-bittorrent;x-scheme-handler/magnet' ] |
549 | }, | 435 | }, |
550 | MAX_RECURSION_COMMENTS: 100, | 436 | MAX_RECURSION_COMMENTS: 100, |
551 | ACTOR_REFRESH_INTERVAL: 3600 * 24 * 1000, // 1 day | 437 | ACTOR_REFRESH_INTERVAL: 3600 * 24 * 1000 * 2, // 2 days |
552 | VIDEO_REFRESH_INTERVAL: 3600 * 24 * 1000 // 1 day | 438 | VIDEO_REFRESH_INTERVAL: 3600 * 24 * 1000 * 2, // 2 days |
439 | VIDEO_PLAYLIST_REFRESH_INTERVAL: 3600 * 24 * 1000 * 2 // 2 days | ||
553 | } | 440 | } |
554 | 441 | ||
555 | const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = { | 442 | const ACTIVITY_PUB_ACTOR_TYPES: { [ id: string ]: ActivityPubActorType } = { |
@@ -575,7 +462,7 @@ const USER_PASSWORD_RESET_LIFETIME = 60000 * 5 // 5 minutes | |||
575 | 462 | ||
576 | const USER_EMAIL_VERIFY_LIFETIME = 60000 * 60 // 60 minutes | 463 | const USER_EMAIL_VERIFY_LIFETIME = 60000 * 60 // 60 minutes |
577 | 464 | ||
578 | const NSFW_POLICY_TYPES: { [ id: string]: NSFWPolicyType } = { | 465 | const NSFW_POLICY_TYPES: { [ id: string ]: NSFWPolicyType } = { |
579 | DO_NOT_LIST: 'do_not_list', | 466 | DO_NOT_LIST: 'do_not_list', |
580 | BLUR: 'blur', | 467 | BLUR: 'blur', |
581 | DISPLAY: 'display' | 468 | DISPLAY: 'display' |
@@ -590,6 +477,9 @@ const STATIC_PATHS = { | |||
590 | TORRENTS: '/static/torrents/', | 477 | TORRENTS: '/static/torrents/', |
591 | WEBSEED: '/static/webseed/', | 478 | WEBSEED: '/static/webseed/', |
592 | REDUNDANCY: '/static/redundancy/', | 479 | REDUNDANCY: '/static/redundancy/', |
480 | STREAMING_PLAYLISTS: { | ||
481 | HLS: '/static/streaming-playlists/hls' | ||
482 | }, | ||
593 | AVATARS: '/static/avatars/', | 483 | AVATARS: '/static/avatars/', |
594 | VIDEO_CAPTIONS: '/static/video-captions/' | 484 | VIDEO_CAPTIONS: '/static/video-captions/' |
595 | } | 485 | } |
@@ -603,8 +493,8 @@ let STATIC_MAX_AGE = '2h' | |||
603 | 493 | ||
604 | // Videos thumbnail size | 494 | // Videos thumbnail size |
605 | const THUMBNAILS_SIZE = { | 495 | const THUMBNAILS_SIZE = { |
606 | width: 200, | 496 | width: 223, |
607 | height: 110 | 497 | height: 122 |
608 | } | 498 | } |
609 | const PREVIEWS_SIZE = { | 499 | const PREVIEWS_SIZE = { |
610 | width: 560, | 500 | width: 560, |
@@ -621,7 +511,7 @@ const EMBED_SIZE = { | |||
621 | } | 511 | } |
622 | 512 | ||
623 | // Sub folders of cache directory | 513 | // Sub folders of cache directory |
624 | const CACHE = { | 514 | const FILES_CACHE = { |
625 | PREVIEWS: { | 515 | PREVIEWS: { |
626 | DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'previews'), | 516 | DIRECTORY: join(CONFIG.STORAGE.CACHE_DIR, 'previews'), |
627 | MAX_AGE: 1000 * 3600 * 3 // 3 hours | 517 | MAX_AGE: 1000 * 3600 * 3 // 3 hours |
@@ -632,6 +522,15 @@ const CACHE = { | |||
632 | } | 522 | } |
633 | } | 523 | } |
634 | 524 | ||
525 | const CACHE = { | ||
526 | USER_TOKENS: { | ||
527 | MAX_SIZE: 10000 | ||
528 | } | ||
529 | } | ||
530 | |||
531 | const HLS_STREAMING_PLAYLIST_DIRECTORY = join(CONFIG.STORAGE.STREAMING_PLAYLISTS_DIR, 'hls') | ||
532 | const HLS_REDUNDANCY_DIRECTORY = join(CONFIG.STORAGE.REDUNDANCY_DIR, 'hls') | ||
533 | |||
635 | const MEMOIZE_TTL = { | 534 | const MEMOIZE_TTL = { |
636 | OVERVIEWS_SAMPLE: 1000 * 3600 * 4 // 4 hours | 535 | OVERVIEWS_SAMPLE: 1000 * 3600 * 4 // 4 hours |
637 | } | 536 | } |
@@ -650,7 +549,7 @@ const CUSTOM_HTML_TAG_COMMENTS = { | |||
650 | TITLE: '<!-- title tag -->', | 549 | TITLE: '<!-- title tag -->', |
651 | DESCRIPTION: '<!-- description tag -->', | 550 | DESCRIPTION: '<!-- description tag -->', |
652 | CUSTOM_CSS: '<!-- custom css tag -->', | 551 | CUSTOM_CSS: '<!-- custom css tag -->', |
653 | OPENGRAPH_AND_OEMBED: '<!-- open graph and oembed tags -->' | 552 | META_TAGS: '<!-- meta tags -->' |
654 | } | 553 | } |
655 | 554 | ||
656 | // --------------------------------------------------------------------------- | 555 | // --------------------------------------------------------------------------- |
@@ -659,6 +558,8 @@ const FEEDS = { | |||
659 | COUNT: 20 | 558 | COUNT: 20 |
660 | } | 559 | } |
661 | 560 | ||
561 | const MAX_LOGS_OUTPUT_CHARACTERS = 10 * 1000 * 1000 | ||
562 | |||
662 | // --------------------------------------------------------------------------- | 563 | // --------------------------------------------------------------------------- |
663 | 564 | ||
664 | const TRACKER_RATE_LIMITS = { | 565 | const TRACKER_RATE_LIMITS = { |
@@ -667,6 +568,8 @@ const TRACKER_RATE_LIMITS = { | |||
667 | ANNOUNCES_PER_IP: 30 // maximum announces for all our torrents in the interval | 568 | ANNOUNCES_PER_IP: 30 // maximum announces for all our torrents in the interval |
668 | } | 569 | } |
669 | 570 | ||
571 | const P2P_MEDIA_LOADER_PEER_VERSION = 2 | ||
572 | |||
670 | // --------------------------------------------------------------------------- | 573 | // --------------------------------------------------------------------------- |
671 | 574 | ||
672 | // Special constants for a test instance | 575 | // Special constants for a test instance |
@@ -683,38 +586,50 @@ if (isTestInstance() === true) { | |||
683 | ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2 | 586 | ACTIVITY_PUB.COLLECTION_ITEMS_PER_PAGE = 2 |
684 | ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds | 587 | ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL = 10 * 1000 // 10 seconds |
685 | ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL = 10 * 1000 // 10 seconds | 588 | ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL = 10 * 1000 // 10 seconds |
589 | ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL = 10 * 1000 // 10 seconds | ||
686 | 590 | ||
687 | CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB | 591 | CONSTRAINTS_FIELDS.ACTORS.AVATAR.FILE_SIZE.max = 100 * 1024 // 100KB |
688 | 592 | ||
689 | SCHEDULER_INTERVALS_MS.actorFollowScores = 1000 | 593 | SCHEDULER_INTERVALS_MS.actorFollowScores = 1000 |
690 | SCHEDULER_INTERVALS_MS.removeOldJobs = 10000 | 594 | SCHEDULER_INTERVALS_MS.removeOldJobs = 10000 |
595 | SCHEDULER_INTERVALS_MS.removeOldHistory = 5000 | ||
596 | SCHEDULER_INTERVALS_MS.removeOldViews = 5000 | ||
691 | SCHEDULER_INTERVALS_MS.updateVideos = 5000 | 597 | SCHEDULER_INTERVALS_MS.updateVideos = 5000 |
692 | REPEAT_JOBS['videos-views'] = { every: 5000 } | 598 | REPEAT_JOBS[ 'videos-views' ] = { every: 5000 } |
693 | 599 | ||
694 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 | 600 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 |
695 | 601 | ||
696 | VIDEO_VIEW_LIFETIME = 1000 // 1 second | 602 | VIDEO_VIEW_LIFETIME = 1000 // 1 second |
697 | CONTACT_FORM_LIFETIME = 1000 // 1 second | 603 | CONTACT_FORM_LIFETIME = 1000 // 1 second |
698 | 604 | ||
699 | JOB_ATTEMPTS['email'] = 1 | 605 | JOB_ATTEMPTS[ 'email' ] = 1 |
700 | 606 | ||
701 | CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 | 607 | FILES_CACHE.VIDEO_CAPTIONS.MAX_AGE = 3000 |
702 | MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1 | 608 | MEMOIZE_TTL.OVERVIEWS_SAMPLE = 1 |
703 | ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms' | 609 | ROUTE_CACHE_LIFETIME.OVERVIEWS.VIDEOS = '0ms' |
610 | |||
611 | RATES_LIMIT.LOGIN.MAX = 20 | ||
704 | } | 612 | } |
705 | 613 | ||
706 | updateWebserverUrls() | 614 | updateWebserverUrls() |
707 | 615 | ||
616 | registerConfigChangedHandler(() => { | ||
617 | updateWebserverUrls() | ||
618 | updateWebserverConfig() | ||
619 | }) | ||
620 | |||
708 | // --------------------------------------------------------------------------- | 621 | // --------------------------------------------------------------------------- |
709 | 622 | ||
710 | export { | 623 | export { |
624 | WEBSERVER, | ||
711 | API_VERSION, | 625 | API_VERSION, |
626 | HLS_REDUNDANCY_DIRECTORY, | ||
627 | P2P_MEDIA_LOADER_PEER_VERSION, | ||
712 | AVATARS_SIZE, | 628 | AVATARS_SIZE, |
713 | ACCEPT_HEADERS, | 629 | ACCEPT_HEADERS, |
714 | BCRYPT_SALT_SIZE, | 630 | BCRYPT_SALT_SIZE, |
715 | TRACKER_RATE_LIMITS, | 631 | TRACKER_RATE_LIMITS, |
716 | CACHE, | 632 | FILES_CACHE, |
717 | CONFIG, | ||
718 | CONSTRAINTS_FIELDS, | 633 | CONSTRAINTS_FIELDS, |
719 | EMBED_SIZE, | 634 | EMBED_SIZE, |
720 | REDUNDANCY, | 635 | REDUNDANCY, |
@@ -733,12 +648,15 @@ export { | |||
733 | PRIVATE_RSA_KEY_SIZE, | 648 | PRIVATE_RSA_KEY_SIZE, |
734 | ROUTE_CACHE_LIFETIME, | 649 | ROUTE_CACHE_LIFETIME, |
735 | SORTABLE_COLUMNS, | 650 | SORTABLE_COLUMNS, |
651 | HLS_STREAMING_PLAYLIST_DIRECTORY, | ||
736 | FEEDS, | 652 | FEEDS, |
737 | JOB_TTL, | 653 | JOB_TTL, |
738 | NSFW_POLICY_TYPES, | 654 | NSFW_POLICY_TYPES, |
739 | STATIC_MAX_AGE, | 655 | STATIC_MAX_AGE, |
740 | STATIC_PATHS, | 656 | STATIC_PATHS, |
741 | VIDEO_IMPORT_TIMEOUT, | 657 | VIDEO_IMPORT_TIMEOUT, |
658 | VIDEO_PLAYLIST_TYPES, | ||
659 | MAX_LOGS_OUTPUT_CHARACTERS, | ||
742 | ACTIVITY_PUB, | 660 | ACTIVITY_PUB, |
743 | ACTIVITY_PUB_ACTOR_TYPES, | 661 | ACTIVITY_PUB_ACTOR_TYPES, |
744 | THUMBNAILS_SIZE, | 662 | THUMBNAILS_SIZE, |
@@ -751,6 +669,7 @@ export { | |||
751 | VIDEO_TRANSCODING_FPS, | 669 | VIDEO_TRANSCODING_FPS, |
752 | FFMPEG_NICE, | 670 | FFMPEG_NICE, |
753 | VIDEO_ABUSE_STATES, | 671 | VIDEO_ABUSE_STATES, |
672 | CACHE, | ||
754 | JOB_REQUEST_TIMEOUT, | 673 | JOB_REQUEST_TIMEOUT, |
755 | USER_PASSWORD_RESET_LIFETIME, | 674 | USER_PASSWORD_RESET_LIFETIME, |
756 | MEMOIZE_TTL, | 675 | MEMOIZE_TTL, |
@@ -767,22 +686,13 @@ export { | |||
767 | VIDEO_IMPORT_STATES, | 686 | VIDEO_IMPORT_STATES, |
768 | VIDEO_VIEW_LIFETIME, | 687 | VIDEO_VIEW_LIFETIME, |
769 | CONTACT_FORM_LIFETIME, | 688 | CONTACT_FORM_LIFETIME, |
689 | VIDEO_PLAYLIST_PRIVACIES, | ||
690 | loadLanguages, | ||
770 | buildLanguages | 691 | buildLanguages |
771 | } | 692 | } |
772 | 693 | ||
773 | // --------------------------------------------------------------------------- | 694 | // --------------------------------------------------------------------------- |
774 | 695 | ||
775 | function getLocalConfigFilePath () { | ||
776 | const configSources = config.util.getConfigSources() | ||
777 | if (configSources.length === 0) throw new Error('Invalid config source.') | ||
778 | |||
779 | let filename = 'local' | ||
780 | if (process.env.NODE_ENV) filename += `-${process.env.NODE_ENV}` | ||
781 | if (process.env.NODE_APP_INSTANCE) filename += `-${process.env.NODE_APP_INSTANCE}` | ||
782 | |||
783 | return join(dirname(configSources[ 0 ].name), filename + '.json') | ||
784 | } | ||
785 | |||
786 | function buildVideoMimetypeExt () { | 696 | function buildVideoMimetypeExt () { |
787 | const data = { | 697 | const data = { |
788 | 'video/webm': '.webm', | 698 | 'video/webm': '.webm', |
@@ -805,8 +715,12 @@ function buildVideoMimetypeExt () { | |||
805 | } | 715 | } |
806 | 716 | ||
807 | function updateWebserverUrls () { | 717 | function updateWebserverUrls () { |
808 | CONFIG.WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT) | 718 | WEBSERVER.URL = sanitizeUrl(CONFIG.WEBSERVER.SCHEME + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT) |
809 | CONFIG.WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP) | 719 | WEBSERVER.HOST = sanitizeHost(CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT, REMOTE_SCHEME.HTTP) |
720 | WEBSERVER.SCHEME = CONFIG.WEBSERVER.SCHEME | ||
721 | WEBSERVER.WS = CONFIG.WEBSERVER.WS | ||
722 | WEBSERVER.HOSTNAME = CONFIG.WEBSERVER.HOSTNAME | ||
723 | WEBSERVER.PORT = CONFIG.WEBSERVER.PORT | ||
810 | } | 724 | } |
811 | 725 | ||
812 | function updateWebserverConfig () { | 726 | function updateWebserverConfig () { |
@@ -822,16 +736,8 @@ function buildVideosExtname () { | |||
822 | : [ '.mp4', '.ogv', '.webm' ] | 736 | : [ '.mp4', '.ogv', '.webm' ] |
823 | } | 737 | } |
824 | 738 | ||
825 | function buildVideosRedundancy (objs: any[]): VideosRedundancy[] { | 739 | function loadLanguages () { |
826 | if (!objs) return [] | 740 | Object.assign(VIDEO_LANGUAGES, buildLanguages()) |
827 | |||
828 | return objs.map(obj => { | ||
829 | return Object.assign({}, obj, { | ||
830 | minLifetime: parseDuration(obj.min_lifetime), | ||
831 | size: bytes.parse(obj.size), | ||
832 | minViews: obj.min_views | ||
833 | }) | ||
834 | }) | ||
835 | } | 741 | } |
836 | 742 | ||
837 | function buildLanguages () { | 743 | function buildLanguages () { |
@@ -866,42 +772,13 @@ function buildLanguages () { | |||
866 | iso639 | 772 | iso639 |
867 | .filter(l => { | 773 | .filter(l => { |
868 | return (l.iso6391 !== null && l.type === 'living') || | 774 | return (l.iso6391 !== null && l.type === 'living') || |
869 | additionalLanguages[l.iso6393] === true | 775 | additionalLanguages[ l.iso6393 ] === true |
870 | }) | 776 | }) |
871 | .forEach(l => languages[l.iso6391 || l.iso6393] = l.name) | 777 | .forEach(l => languages[ l.iso6391 || l.iso6393 ] = l.name) |
872 | 778 | ||
873 | // Override Occitan label | 779 | // Override Occitan label |
874 | languages['oc'] = 'Occitan' | 780 | languages[ 'oc' ] = 'Occitan' |
781 | languages[ 'el' ] = 'Greek' | ||
875 | 782 | ||
876 | return languages | 783 | return languages |
877 | } | 784 | } |
878 | |||
879 | export function reloadConfig () { | ||
880 | |||
881 | function directory () { | ||
882 | if (process.env.NODE_CONFIG_DIR) { | ||
883 | return process.env.NODE_CONFIG_DIR | ||
884 | } | ||
885 | |||
886 | return join(root(), 'config') | ||
887 | } | ||
888 | |||
889 | function purge () { | ||
890 | for (const fileName in require.cache) { | ||
891 | if (-1 === fileName.indexOf(directory())) { | ||
892 | continue | ||
893 | } | ||
894 | |||
895 | delete require.cache[fileName] | ||
896 | } | ||
897 | |||
898 | delete require.cache[require.resolve('config')] | ||
899 | } | ||
900 | |||
901 | purge() | ||
902 | |||
903 | config = require('config') | ||
904 | |||
905 | updateWebserverConfig() | ||
906 | updateWebserverUrls() | ||
907 | } | ||
diff --git a/server/initializers/database.ts b/server/initializers/database.ts index 84ad2079b..142063a99 100644 --- a/server/initializers/database.ts +++ b/server/initializers/database.ts | |||
@@ -21,7 +21,7 @@ import { VideoCommentModel } from '../models/video/video-comment' | |||
21 | import { VideoFileModel } from '../models/video/video-file' | 21 | import { VideoFileModel } from '../models/video/video-file' |
22 | import { VideoShareModel } from '../models/video/video-share' | 22 | import { VideoShareModel } from '../models/video/video-share' |
23 | import { VideoTagModel } from '../models/video/video-tag' | 23 | import { VideoTagModel } from '../models/video/video-tag' |
24 | import { CONFIG } from './constants' | 24 | import { CONFIG } from './config' |
25 | import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' | 25 | import { ScheduleVideoUpdateModel } from '../models/video/schedule-video-update' |
26 | import { VideoCaptionModel } from '../models/video/video-caption' | 26 | import { VideoCaptionModel } from '../models/video/video-caption' |
27 | import { VideoImportModel } from '../models/video/video-import' | 27 | import { VideoImportModel } from '../models/video/video-import' |
@@ -33,6 +33,11 @@ import { AccountBlocklistModel } from '../models/account/account-blocklist' | |||
33 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | 33 | import { ServerBlocklistModel } from '../models/server/server-blocklist' |
34 | import { UserNotificationModel } from '../models/account/user-notification' | 34 | import { UserNotificationModel } from '../models/account/user-notification' |
35 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | 35 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' |
36 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
37 | import { VideoPlaylistModel } from '../models/video/video-playlist' | ||
38 | import { VideoPlaylistElementModel } from '../models/video/video-playlist-element' | ||
39 | import { ThumbnailModel } from '../models/video/thumbnail' | ||
40 | import { QueryTypes, Transaction } from 'sequelize' | ||
36 | 41 | ||
37 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string | 42 | require('pg').defaults.parseInt8 = true // Avoid BIGINT to be converted to string |
38 | 43 | ||
@@ -54,8 +59,7 @@ const sequelizeTypescript = new SequelizeTypescript({ | |||
54 | max: poolMax | 59 | max: poolMax |
55 | }, | 60 | }, |
56 | benchmark: isTestInstance(), | 61 | benchmark: isTestInstance(), |
57 | isolationLevel: SequelizeTypescript.Transaction.ISOLATION_LEVELS.SERIALIZABLE, | 62 | isolationLevel: Transaction.ISOLATION_LEVELS.SERIALIZABLE, |
58 | operatorsAliases: false, | ||
59 | logging: (message: string, benchmark: number) => { | 63 | logging: (message: string, benchmark: number) => { |
60 | if (process.env.NODE_DB_LOG === 'false') return | 64 | if (process.env.NODE_DB_LOG === 'false') return |
61 | 65 | ||
@@ -82,6 +86,7 @@ async function initDatabaseModels (silent: boolean) { | |||
82 | AccountVideoRateModel, | 86 | AccountVideoRateModel, |
83 | UserModel, | 87 | UserModel, |
84 | VideoAbuseModel, | 88 | VideoAbuseModel, |
89 | VideoModel, | ||
85 | VideoChangeOwnershipModel, | 90 | VideoChangeOwnershipModel, |
86 | VideoChannelModel, | 91 | VideoChannelModel, |
87 | VideoShareModel, | 92 | VideoShareModel, |
@@ -89,7 +94,6 @@ async function initDatabaseModels (silent: boolean) { | |||
89 | VideoCaptionModel, | 94 | VideoCaptionModel, |
90 | VideoBlacklistModel, | 95 | VideoBlacklistModel, |
91 | VideoTagModel, | 96 | VideoTagModel, |
92 | VideoModel, | ||
93 | VideoCommentModel, | 97 | VideoCommentModel, |
94 | ScheduleVideoUpdateModel, | 98 | ScheduleVideoUpdateModel, |
95 | VideoImportModel, | 99 | VideoImportModel, |
@@ -99,7 +103,11 @@ async function initDatabaseModels (silent: boolean) { | |||
99 | AccountBlocklistModel, | 103 | AccountBlocklistModel, |
100 | ServerBlocklistModel, | 104 | ServerBlocklistModel, |
101 | UserNotificationModel, | 105 | UserNotificationModel, |
102 | UserNotificationSettingModel | 106 | UserNotificationSettingModel, |
107 | VideoStreamingPlaylistModel, | ||
108 | VideoPlaylistModel, | ||
109 | VideoPlaylistElementModel, | ||
110 | ThumbnailModel | ||
103 | ]) | 111 | ]) |
104 | 112 | ||
105 | // Check extensions exist in the database | 113 | // Check extensions exist in the database |
@@ -132,11 +140,16 @@ async function checkPostgresExtensions () { | |||
132 | } | 140 | } |
133 | 141 | ||
134 | async function checkPostgresExtension (extension: string) { | 142 | async function checkPostgresExtension (extension: string) { |
135 | const query = `SELECT true AS enabled FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;` | 143 | const query = `SELECT 1 FROM pg_available_extensions WHERE name = '${extension}' AND installed_version IS NOT NULL;` |
136 | const [ res ] = await sequelizeTypescript.query(query, { raw: true }) | 144 | const options = { |
145 | type: QueryTypes.SELECT as QueryTypes.SELECT, | ||
146 | raw: true | ||
147 | } | ||
148 | |||
149 | const res = await sequelizeTypescript.query<object>(query, options) | ||
137 | 150 | ||
138 | if (!res || res.length === 0 || res[ 0 ][ 'enabled' ] !== true) { | 151 | if (!res || res.length === 0) { |
139 | // Try to create the extension ourself | 152 | // Try to create the extension ourselves |
140 | try { | 153 | try { |
141 | await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true }) | 154 | await sequelizeTypescript.query(`CREATE EXTENSION ${extension};`, { raw: true }) |
142 | 155 | ||
diff --git a/server/initializers/index.ts b/server/initializers/index.ts index fe9190a9c..0fc1a7363 100644 --- a/server/initializers/index.ts +++ b/server/initializers/index.ts | |||
@@ -1,5 +1,3 @@ | |||
1 | // Constants first, database in second! | ||
2 | export * from './constants' | ||
3 | export * from './database' | 1 | export * from './database' |
4 | export * from './installer' | 2 | export * from './installer' |
5 | export * from './migrator' | 3 | export * from './migrator' |
diff --git a/server/initializers/installer.ts b/server/initializers/installer.ts index b9a9da183..127449577 100644 --- a/server/initializers/installer.ts +++ b/server/initializers/installer.ts | |||
@@ -1,14 +1,15 @@ | |||
1 | import * as passwordGenerator from 'password-generator' | 1 | import * as passwordGenerator from 'password-generator' |
2 | import { UserRole } from '../../shared' | 2 | import { UserRole } from '../../shared' |
3 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
4 | import { createApplicationActor, createUserAccountAndChannel } from '../lib/user' | 4 | import { createApplicationActor, createUserAccountAndChannelAndPlaylist } from '../lib/user' |
5 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
6 | import { ApplicationModel } from '../models/application/application' | 6 | import { ApplicationModel } from '../models/application/application' |
7 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 7 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
8 | import { applicationExist, clientsExist, usersExist } from './checker-after-init' | 8 | import { applicationExist, clientsExist, usersExist } from './checker-after-init' |
9 | import { CACHE, CONFIG, LAST_MIGRATION_VERSION } from './constants' | 9 | import { FILES_CACHE, HLS_STREAMING_PLAYLIST_DIRECTORY, LAST_MIGRATION_VERSION } from './constants' |
10 | import { sequelizeTypescript } from './database' | 10 | import { sequelizeTypescript } from './database' |
11 | import { remove, ensureDir } from 'fs-extra' | 11 | import { ensureDir, remove } from 'fs-extra' |
12 | import { CONFIG } from './config' | ||
12 | 13 | ||
13 | async function installApplication () { | 14 | async function installApplication () { |
14 | try { | 15 | try { |
@@ -24,7 +25,7 @@ async function installApplication () { | |||
24 | }), | 25 | }), |
25 | 26 | ||
26 | // Directories | 27 | // Directories |
27 | removeCacheDirectories() | 28 | removeCacheAndTmpDirectories() |
28 | .then(() => createDirectoriesIfNotExist()) | 29 | .then(() => createDirectoriesIfNotExist()) |
29 | ]) | 30 | ]) |
30 | } catch (err) { | 31 | } catch (err) { |
@@ -41,9 +42,9 @@ export { | |||
41 | 42 | ||
42 | // --------------------------------------------------------------------------- | 43 | // --------------------------------------------------------------------------- |
43 | 44 | ||
44 | function removeCacheDirectories () { | 45 | function removeCacheAndTmpDirectories () { |
45 | const cacheDirectories = Object.keys(CACHE) | 46 | const cacheDirectories = Object.keys(FILES_CACHE) |
46 | .map(k => CACHE[k].DIRECTORY) | 47 | .map(k => FILES_CACHE[k].DIRECTORY) |
47 | 48 | ||
48 | const tasks: Promise<any>[] = [] | 49 | const tasks: Promise<any>[] = [] |
49 | 50 | ||
@@ -53,13 +54,15 @@ function removeCacheDirectories () { | |||
53 | tasks.push(remove(dir)) | 54 | tasks.push(remove(dir)) |
54 | } | 55 | } |
55 | 56 | ||
57 | tasks.push(remove(CONFIG.STORAGE.TMP_DIR)) | ||
58 | |||
56 | return Promise.all(tasks) | 59 | return Promise.all(tasks) |
57 | } | 60 | } |
58 | 61 | ||
59 | function createDirectoriesIfNotExist () { | 62 | function createDirectoriesIfNotExist () { |
60 | const storage = CONFIG.STORAGE | 63 | const storage = CONFIG.STORAGE |
61 | const cacheDirectories = Object.keys(CACHE) | 64 | const cacheDirectories = Object.keys(FILES_CACHE) |
62 | .map(k => CACHE[k].DIRECTORY) | 65 | .map(k => FILES_CACHE[k].DIRECTORY) |
63 | 66 | ||
64 | const tasks: Promise<void>[] = [] | 67 | const tasks: Promise<void>[] = [] |
65 | for (const key of Object.keys(storage)) { | 68 | for (const key of Object.keys(storage)) { |
@@ -73,6 +76,9 @@ function createDirectoriesIfNotExist () { | |||
73 | tasks.push(ensureDir(dir)) | 76 | tasks.push(ensureDir(dir)) |
74 | } | 77 | } |
75 | 78 | ||
79 | // Playlist directories | ||
80 | tasks.push(ensureDir(HLS_STREAMING_PLAYLIST_DIRECTORY)) | ||
81 | |||
76 | return Promise.all(tasks) | 82 | return Promise.all(tasks) |
77 | } | 83 | } |
78 | 84 | ||
@@ -138,7 +144,7 @@ async function createOAuthAdminIfNotExist () { | |||
138 | } | 144 | } |
139 | const user = new UserModel(userData) | 145 | const user = new UserModel(userData) |
140 | 146 | ||
141 | await createUserAccountAndChannel(user, validatePassword) | 147 | await createUserAccountAndChannelAndPlaylist(user, validatePassword) |
142 | logger.info('Username: ' + username) | 148 | logger.info('Username: ' + username) |
143 | logger.info('User password: ' + password) | 149 | logger.info('User password: ' + password) |
144 | } | 150 | } |
diff --git a/server/initializers/migrations/0075-video-resolutions.ts b/server/initializers/migrations/0075-video-resolutions.ts index 26a188e5e..e4f26cb77 100644 --- a/server/initializers/migrations/0075-video-resolutions.ts +++ b/server/initializers/migrations/0075-video-resolutions.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { join } from 'path' | 2 | import { join } from 'path' |
3 | import { CONFIG } from '../../initializers/constants' | 3 | import { CONFIG } from '../../initializers/config' |
4 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' | 4 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' |
5 | import { readdir, rename } from 'fs-extra' | 5 | import { readdir, rename } from 'fs-extra' |
6 | 6 | ||
diff --git a/server/initializers/migrations/0080-video-channels.ts b/server/initializers/migrations/0080-video-channels.ts index f19721517..5512bdcf1 100644 --- a/server/initializers/migrations/0080-video-channels.ts +++ b/server/initializers/migrations/0080-video-channels.ts | |||
@@ -69,12 +69,12 @@ async function up (utils: { | |||
69 | const options = { | 69 | const options = { |
70 | type: Sequelize.QueryTypes.SELECT | 70 | type: Sequelize.QueryTypes.SELECT |
71 | } | 71 | } |
72 | const rawVideos = await utils.sequelize.query(query, options) | 72 | const rawVideos = await utils.sequelize.query(query, options) as any |
73 | 73 | ||
74 | for (const rawVideo of rawVideos) { | 74 | for (const rawVideo of rawVideos) { |
75 | const videoChannel = await utils.db.VideoChannel.findOne({ where: { authorId: rawVideo.authorId } }) | 75 | const videoChannel = await utils.db.VideoChannel.findOne({ where: { authorId: rawVideo.authorId } }) |
76 | 76 | ||
77 | const video = await utils.db.Video.findById(rawVideo.id) | 77 | const video = await utils.db.Video.findByPk(rawVideo.id) |
78 | video.channelId = videoChannel.id | 78 | video.channelId = videoChannel.id |
79 | await video.save() | 79 | await video.save() |
80 | } | 80 | } |
diff --git a/server/initializers/migrations/0100-activitypub.ts b/server/initializers/migrations/0100-activitypub.ts index a7ebd804c..2880a97d9 100644 --- a/server/initializers/migrations/0100-activitypub.ts +++ b/server/initializers/migrations/0100-activitypub.ts | |||
@@ -21,7 +21,7 @@ async function up (utils: { | |||
21 | const options = { | 21 | const options = { |
22 | type: Sequelize.QueryTypes.SELECT | 22 | type: Sequelize.QueryTypes.SELECT |
23 | } | 23 | } |
24 | const res = await utils.sequelize.query(query, options) | 24 | const res = await utils.sequelize.query(query, options) as any |
25 | 25 | ||
26 | if (!res[0] || res[0].total !== 0) { | 26 | if (!res[0] || res[0].total !== 0) { |
27 | throw new Error('You need to quit friends.') | 27 | throw new Error('You need to quit friends.') |
@@ -68,8 +68,8 @@ async function up (utils: { | |||
68 | const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined) | 68 | const accountCreated = await createLocalAccountWithoutKeys(SERVER_ACTOR_NAME, null, applicationInstance.id, undefined) |
69 | 69 | ||
70 | const { publicKey, privateKey } = await createPrivateAndPublicKeys() | 70 | const { publicKey, privateKey } = await createPrivateAndPublicKeys() |
71 | accountCreated.set('publicKey', publicKey) | 71 | accountCreated.Actor.publicKey = publicKey |
72 | accountCreated.set('privateKey', privateKey) | 72 | accountCreated.Actor.privateKey = privateKey |
73 | 73 | ||
74 | await accountCreated.save() | 74 | await accountCreated.save() |
75 | } | 75 | } |
@@ -86,8 +86,8 @@ async function up (utils: { | |||
86 | const account = await createLocalAccountWithoutKeys(user.username, user.id, null, undefined) | 86 | const account = await createLocalAccountWithoutKeys(user.username, user.id, null, undefined) |
87 | 87 | ||
88 | const { publicKey, privateKey } = await createPrivateAndPublicKeys() | 88 | const { publicKey, privateKey } = await createPrivateAndPublicKeys() |
89 | account.set('publicKey', publicKey) | 89 | account.Actor.publicKey = publicKey |
90 | account.set('privateKey', privateKey) | 90 | account.Actor.privateKey = privateKey |
91 | await account.save() | 91 | await account.save() |
92 | } | 92 | } |
93 | 93 | ||
diff --git a/server/initializers/migrations/0135-video-channel-actor.ts b/server/initializers/migrations/0135-video-channel-actor.ts index 033f43b68..5ace0f4d2 100644 --- a/server/initializers/migrations/0135-video-channel-actor.ts +++ b/server/initializers/migrations/0135-video-channel-actor.ts | |||
@@ -239,7 +239,8 @@ async function up (utils: { | |||
239 | 239 | ||
240 | { | 240 | { |
241 | const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL' | 241 | const query = 'SELECT * FROM "actor" WHERE "serverId" IS NULL AND "publicKey" IS NULL' |
242 | const [ res ] = await utils.sequelize.query(query) | 242 | const options = { type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT } |
243 | const [ res ] = await utils.sequelize.query(query, options) | ||
243 | 244 | ||
244 | for (const actor of res) { | 245 | for (const actor of res) { |
245 | const { privateKey, publicKey } = await createPrivateAndPublicKeys() | 246 | const { privateKey, publicKey } = await createPrivateAndPublicKeys() |
diff --git a/server/initializers/migrations/0140-actor-url.ts b/server/initializers/migrations/0140-actor-url.ts index e64ee3487..020499391 100644 --- a/server/initializers/migrations/0140-actor-url.ts +++ b/server/initializers/migrations/0140-actor-url.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { CONFIG } from '../constants' | 2 | import { WEBSERVER } from '../constants' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction, |
6 | queryInterface: Sequelize.QueryInterface, | 6 | queryInterface: Sequelize.QueryInterface, |
7 | sequelize: Sequelize.Sequelize | 7 | sequelize: Sequelize.Sequelize |
8 | }): Promise<void> { | 8 | }): Promise<void> { |
9 | const toReplace = CONFIG.WEBSERVER.HOSTNAME + ':443' | 9 | const toReplace = WEBSERVER.HOSTNAME + ':443' |
10 | const by = CONFIG.WEBSERVER.HOST | 10 | const by = WEBSERVER.HOST |
11 | const replacer = column => `replace("${column}", '${toReplace}', '${by}')` | 11 | const replacer = column => `replace("${column}", '${toReplace}', '${by}')` |
12 | 12 | ||
13 | { | 13 | { |
diff --git a/server/initializers/migrations/0170-actor-follow-score.ts b/server/initializers/migrations/0170-actor-follow-score.ts index 2deabaf98..a12b35da9 100644 --- a/server/initializers/migrations/0170-actor-follow-score.ts +++ b/server/initializers/migrations/0170-actor-follow-score.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { ACTOR_FOLLOW_SCORE } from '../index' | 2 | import { ACTOR_FOLLOW_SCORE } from '../constants' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction, |
diff --git a/server/initializers/migrations/0210-video-language.ts b/server/initializers/migrations/0210-video-language.ts index b7ec90905..ca95c7527 100644 --- a/server/initializers/migrations/0210-video-language.ts +++ b/server/initializers/migrations/0210-video-language.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { CONSTRAINTS_FIELDS } from '../index' | 2 | import { CONSTRAINTS_FIELDS } from '../constants' |
3 | 3 | ||
4 | async function up (utils: { | 4 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 5 | transaction: Sequelize.Transaction, |
diff --git a/server/initializers/migrations/0215-video-support-length.ts b/server/initializers/migrations/0215-video-support-length.ts index 994eda60d..ba395050f 100644 --- a/server/initializers/migrations/0215-video-support-length.ts +++ b/server/initializers/migrations/0215-video-support-length.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { CONSTRAINTS_FIELDS } from '../index' | ||
3 | 2 | ||
4 | async function up (utils: { | 3 | async function up (utils: { |
5 | transaction: Sequelize.Transaction, | 4 | transaction: Sequelize.Transaction, |
diff --git a/server/initializers/migrations/0235-delete-some-video-indexes.ts b/server/initializers/migrations/0235-delete-some-video-indexes.ts index e362f240c..5964b0dc5 100644 --- a/server/initializers/migrations/0235-delete-some-video-indexes.ts +++ b/server/initializers/migrations/0235-delete-some-video-indexes.ts | |||
@@ -1,8 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { createClient } from 'redis' | ||
3 | import { CONFIG } from '../constants' | ||
4 | import { JobQueue } from '../../lib/job-queue' | ||
5 | import { initDatabaseModels } from '../database' | ||
6 | 2 | ||
7 | async function up (utils: { | 3 | async function up (utils: { |
8 | transaction: Sequelize.Transaction | 4 | transaction: Sequelize.Transaction |
diff --git a/server/initializers/migrations/0240-drop-old-indexes.ts b/server/initializers/migrations/0240-drop-old-indexes.ts index ba961e3f9..39868fa2d 100644 --- a/server/initializers/migrations/0240-drop-old-indexes.ts +++ b/server/initializers/migrations/0240-drop-old-indexes.ts | |||
@@ -1,8 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { createClient } from 'redis' | ||
3 | import { CONFIG } from '../constants' | ||
4 | import { JobQueue } from '../../lib/job-queue' | ||
5 | import { initDatabaseModels } from '../database' | ||
6 | 2 | ||
7 | async function up (utils: { | 3 | async function up (utils: { |
8 | transaction: Sequelize.Transaction | 4 | transaction: Sequelize.Transaction |
diff --git a/server/initializers/migrations/0330-video-streaming-playlist.ts b/server/initializers/migrations/0330-video-streaming-playlist.ts new file mode 100644 index 000000000..c85a762ab --- /dev/null +++ b/server/initializers/migrations/0330-video-streaming-playlist.ts | |||
@@ -0,0 +1,51 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | }): Promise<void> { | ||
8 | |||
9 | { | ||
10 | const query = ` | ||
11 | CREATE TABLE IF NOT EXISTS "videoStreamingPlaylist" | ||
12 | ( | ||
13 | "id" SERIAL, | ||
14 | "type" INTEGER NOT NULL, | ||
15 | "playlistUrl" VARCHAR(2000) NOT NULL, | ||
16 | "p2pMediaLoaderInfohashes" VARCHAR(255)[] NOT NULL, | ||
17 | "segmentsSha256Url" VARCHAR(255) NOT NULL, | ||
18 | "videoId" INTEGER NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
19 | "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, | ||
20 | "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, | ||
21 | PRIMARY KEY ("id") | ||
22 | );` | ||
23 | await utils.sequelize.query(query) | ||
24 | } | ||
25 | |||
26 | { | ||
27 | const data = { | ||
28 | type: Sequelize.INTEGER, | ||
29 | allowNull: true, | ||
30 | defaultValue: null | ||
31 | } | ||
32 | |||
33 | await utils.queryInterface.changeColumn('videoRedundancy', 'videoFileId', data) | ||
34 | } | ||
35 | |||
36 | { | ||
37 | const query = 'ALTER TABLE "videoRedundancy" ADD COLUMN "videoStreamingPlaylistId" INTEGER NULL ' + | ||
38 | 'REFERENCES "videoStreamingPlaylist" ("id") ON DELETE CASCADE ON UPDATE CASCADE' | ||
39 | |||
40 | await utils.sequelize.query(query) | ||
41 | } | ||
42 | } | ||
43 | |||
44 | function down (options) { | ||
45 | throw new Error('Not implemented.') | ||
46 | } | ||
47 | |||
48 | export { | ||
49 | up, | ||
50 | down | ||
51 | } | ||
diff --git a/server/initializers/migrations/0335-video-downloading-enabled.ts b/server/initializers/migrations/0335-video-downloading-enabled.ts new file mode 100644 index 000000000..e79466447 --- /dev/null +++ b/server/initializers/migrations/0335-video-downloading-enabled.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { Migration } from '../../models/migrations' | ||
3 | |||
4 | async function up (utils: { | ||
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize | ||
8 | }): Promise<void> { | ||
9 | const data = { | ||
10 | type: Sequelize.BOOLEAN, | ||
11 | allowNull: false, | ||
12 | defaultValue: true | ||
13 | } as Migration.Boolean | ||
14 | await utils.queryInterface.addColumn('video', 'downloadEnabled', data) | ||
15 | |||
16 | data.defaultValue = null | ||
17 | return utils.queryInterface.changeColumn('video', 'downloadEnabled', data) | ||
18 | } | ||
19 | |||
20 | function down (options) { | ||
21 | throw new Error('Not implemented.') | ||
22 | } | ||
23 | |||
24 | export { | ||
25 | up, | ||
26 | down | ||
27 | } | ||
diff --git a/server/initializers/migrations/0340-add-originally-published-at.ts b/server/initializers/migrations/0340-add-originally-published-at.ts new file mode 100644 index 000000000..fe4f4a5f9 --- /dev/null +++ b/server/initializers/migrations/0340-add-originally-published-at.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize | ||
7 | }): Promise<void> { | ||
8 | |||
9 | const data = { | ||
10 | type: Sequelize.DATE, | ||
11 | allowNull: true, | ||
12 | defaultValue: null | ||
13 | } | ||
14 | await utils.queryInterface.addColumn('video', 'originallyPublishedAt', data) | ||
15 | |||
16 | } | ||
17 | |||
18 | function down (options) { | ||
19 | throw new Error('Not implemented.') | ||
20 | } | ||
21 | |||
22 | export { | ||
23 | up, | ||
24 | down | ||
25 | } | ||
diff --git a/server/initializers/migrations/0345-video-playlists.ts b/server/initializers/migrations/0345-video-playlists.ts new file mode 100644 index 000000000..de69f5b9e --- /dev/null +++ b/server/initializers/migrations/0345-video-playlists.ts | |||
@@ -0,0 +1,88 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { VideoPlaylistPrivacy, VideoPlaylistType } from '../../../shared/models/videos' | ||
3 | import * as uuidv4 from 'uuid/v4' | ||
4 | import { WEBSERVER } from '../constants' | ||
5 | |||
6 | async function up (utils: { | ||
7 | transaction: Sequelize.Transaction, | ||
8 | queryInterface: Sequelize.QueryInterface, | ||
9 | sequelize: Sequelize.Sequelize | ||
10 | }): Promise<void> { | ||
11 | const transaction = utils.transaction | ||
12 | |||
13 | { | ||
14 | const query = ` | ||
15 | CREATE TABLE IF NOT EXISTS "videoPlaylist" | ||
16 | ( | ||
17 | "id" SERIAL, | ||
18 | "name" VARCHAR(255) NOT NULL, | ||
19 | "description" VARCHAR(255), | ||
20 | "privacy" INTEGER NOT NULL, | ||
21 | "url" VARCHAR(2000) NOT NULL, | ||
22 | "uuid" UUID NOT NULL, | ||
23 | "type" INTEGER NOT NULL DEFAULT 1, | ||
24 | "ownerAccountId" INTEGER NOT NULL REFERENCES "account" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
25 | "videoChannelId" INTEGER REFERENCES "videoChannel" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
26 | "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, | ||
27 | "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, | ||
28 | PRIMARY KEY ("id") | ||
29 | );` | ||
30 | await utils.sequelize.query(query, { transaction }) | ||
31 | } | ||
32 | |||
33 | { | ||
34 | const query = ` | ||
35 | CREATE TABLE IF NOT EXISTS "videoPlaylistElement" | ||
36 | ( | ||
37 | "id" SERIAL, | ||
38 | "url" VARCHAR(2000) NOT NULL, | ||
39 | "position" INTEGER NOT NULL DEFAULT 1, | ||
40 | "startTimestamp" INTEGER, | ||
41 | "stopTimestamp" INTEGER, | ||
42 | "videoPlaylistId" INTEGER NOT NULL REFERENCES "videoPlaylist" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
43 | "videoId" INTEGER NOT NULL REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
44 | "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, | ||
45 | "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, | ||
46 | PRIMARY KEY ("id") | ||
47 | );` | ||
48 | |||
49 | await utils.sequelize.query(query, { transaction }) | ||
50 | } | ||
51 | |||
52 | { | ||
53 | const userQuery = 'SELECT "username" FROM "user";' | ||
54 | |||
55 | const options = { transaction, type: Sequelize.QueryTypes.SELECT as Sequelize.QueryTypes.SELECT } | ||
56 | const userResult = await utils.sequelize.query<{ username: string }>(userQuery, options) | ||
57 | const usernames = userResult.map(r => r.username) | ||
58 | |||
59 | for (const username of usernames) { | ||
60 | const uuid = uuidv4() | ||
61 | |||
62 | const baseUrl = WEBSERVER.URL + '/video-playlists/' + uuid | ||
63 | const query = ` | ||
64 | INSERT INTO "videoPlaylist" ("url", "uuid", "name", "privacy", "type", "ownerAccountId", "createdAt", "updatedAt") | ||
65 | SELECT '${baseUrl}' AS "url", | ||
66 | '${uuid}' AS "uuid", | ||
67 | 'Watch later' AS "name", | ||
68 | ${VideoPlaylistPrivacy.PRIVATE} AS "privacy", | ||
69 | ${VideoPlaylistType.WATCH_LATER} AS "type", | ||
70 | "account"."id" AS "ownerAccountId", | ||
71 | NOW() as "createdAt", | ||
72 | NOW() as "updatedAt" | ||
73 | FROM "user" INNER JOIN "account" ON "user"."id" = "account"."userId" | ||
74 | WHERE "user"."username" = '${username}'` | ||
75 | |||
76 | await utils.sequelize.query(query, { transaction }) | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | |||
81 | function down (options) { | ||
82 | throw new Error('Not implemented.') | ||
83 | } | ||
84 | |||
85 | export { | ||
86 | up, | ||
87 | down | ||
88 | } | ||
diff --git a/server/initializers/migrations/0350-video-blacklist-type.ts b/server/initializers/migrations/0350-video-blacklist-type.ts new file mode 100644 index 000000000..4849020ef --- /dev/null +++ b/server/initializers/migrations/0350-video-blacklist-type.ts | |||
@@ -0,0 +1,64 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { VideoBlacklistType } from '../../../shared/models/videos' | ||
3 | |||
4 | async function up (utils: { | ||
5 | transaction: Sequelize.Transaction, | ||
6 | queryInterface: Sequelize.QueryInterface, | ||
7 | sequelize: Sequelize.Sequelize, | ||
8 | db: any | ||
9 | }): Promise<void> { | ||
10 | { | ||
11 | const data = { | ||
12 | type: Sequelize.INTEGER, | ||
13 | allowNull: true, | ||
14 | defaultValue: null | ||
15 | } | ||
16 | |||
17 | await utils.queryInterface.addColumn('videoBlacklist', 'type', data) | ||
18 | } | ||
19 | |||
20 | { | ||
21 | const query = 'UPDATE "videoBlacklist" SET "type" = ' + VideoBlacklistType.MANUAL | ||
22 | await utils.sequelize.query(query) | ||
23 | } | ||
24 | |||
25 | { | ||
26 | const data = { | ||
27 | type: Sequelize.INTEGER, | ||
28 | allowNull: false, | ||
29 | defaultValue: null | ||
30 | } | ||
31 | await utils.queryInterface.changeColumn('videoBlacklist', 'type', data) | ||
32 | } | ||
33 | |||
34 | { | ||
35 | const data = { | ||
36 | type: Sequelize.INTEGER, | ||
37 | defaultValue: null, | ||
38 | allowNull: true | ||
39 | } | ||
40 | await utils.queryInterface.addColumn('userNotificationSetting', 'videoAutoBlacklistAsModerator', data) | ||
41 | } | ||
42 | |||
43 | { | ||
44 | const query = 'UPDATE "userNotificationSetting" SET "videoAutoBlacklistAsModerator" = 3' | ||
45 | await utils.sequelize.query(query) | ||
46 | } | ||
47 | |||
48 | { | ||
49 | const data = { | ||
50 | type: Sequelize.INTEGER, | ||
51 | defaultValue: null, | ||
52 | allowNull: false | ||
53 | } | ||
54 | await utils.queryInterface.changeColumn('userNotificationSetting', 'videoAutoBlacklistAsModerator', data) | ||
55 | } | ||
56 | } | ||
57 | function down (options) { | ||
58 | throw new Error('Not implemented.') | ||
59 | } | ||
60 | |||
61 | export { | ||
62 | up, | ||
63 | down | ||
64 | } | ||
diff --git a/server/initializers/migrations/0355-p2p-peer-version.ts b/server/initializers/migrations/0355-p2p-peer-version.ts new file mode 100644 index 000000000..18f23d9b7 --- /dev/null +++ b/server/initializers/migrations/0355-p2p-peer-version.ts | |||
@@ -0,0 +1,41 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | |||
10 | { | ||
11 | const data = { | ||
12 | type: Sequelize.INTEGER, | ||
13 | allowNull: true, | ||
14 | defaultValue: null | ||
15 | } | ||
16 | await utils.queryInterface.addColumn('videoStreamingPlaylist', 'p2pMediaLoaderPeerVersion', data) | ||
17 | } | ||
18 | |||
19 | { | ||
20 | const query = `UPDATE "videoStreamingPlaylist" SET "p2pMediaLoaderPeerVersion" = 0;` | ||
21 | await utils.sequelize.query(query) | ||
22 | } | ||
23 | |||
24 | { | ||
25 | const data = { | ||
26 | type: Sequelize.INTEGER, | ||
27 | allowNull: false, | ||
28 | defaultValue: null | ||
29 | } | ||
30 | await utils.queryInterface.changeColumn('videoStreamingPlaylist', 'p2pMediaLoaderPeerVersion', data) | ||
31 | } | ||
32 | } | ||
33 | |||
34 | function down (options) { | ||
35 | throw new Error('Not implemented.') | ||
36 | } | ||
37 | |||
38 | export { | ||
39 | up, | ||
40 | down | ||
41 | } | ||
diff --git a/server/initializers/migrations/0360-notification-instance-follower.ts b/server/initializers/migrations/0360-notification-instance-follower.ts new file mode 100644 index 000000000..05caf8e1d --- /dev/null +++ b/server/initializers/migrations/0360-notification-instance-follower.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.INTEGER, | ||
12 | defaultValue: null, | ||
13 | allowNull: true | ||
14 | } | ||
15 | await utils.queryInterface.addColumn('userNotificationSetting', 'newInstanceFollower', data) | ||
16 | } | ||
17 | |||
18 | { | ||
19 | const query = 'UPDATE "userNotificationSetting" SET "newInstanceFollower" = 1' | ||
20 | await utils.sequelize.query(query) | ||
21 | } | ||
22 | |||
23 | { | ||
24 | const data = { | ||
25 | type: Sequelize.INTEGER, | ||
26 | defaultValue: null, | ||
27 | allowNull: false | ||
28 | } | ||
29 | await utils.queryInterface.changeColumn('userNotificationSetting', 'newInstanceFollower', data) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | function down (options) { | ||
34 | throw new Error('Not implemented.') | ||
35 | } | ||
36 | |||
37 | export { | ||
38 | up, | ||
39 | down | ||
40 | } | ||
diff --git a/server/initializers/migrations/0365-user-admin-flags.ts b/server/initializers/migrations/0365-user-admin-flags.ts new file mode 100644 index 000000000..20553100a --- /dev/null +++ b/server/initializers/migrations/0365-user-admin-flags.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.INTEGER, | ||
12 | defaultValue: null, | ||
13 | allowNull: true | ||
14 | } | ||
15 | await utils.queryInterface.addColumn('user', 'adminFlags', data) | ||
16 | } | ||
17 | |||
18 | { | ||
19 | const query = 'UPDATE "user" SET "adminFlags" = 0' | ||
20 | await utils.sequelize.query(query) | ||
21 | } | ||
22 | |||
23 | { | ||
24 | const data = { | ||
25 | type: Sequelize.INTEGER, | ||
26 | defaultValue: null, | ||
27 | allowNull: false | ||
28 | } | ||
29 | await utils.queryInterface.changeColumn('user', 'adminFlags', data) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | function down (options) { | ||
34 | throw new Error('Not implemented.') | ||
35 | } | ||
36 | |||
37 | export { | ||
38 | up, | ||
39 | down | ||
40 | } | ||
diff --git a/server/initializers/migrations/0370-thumbnail.ts b/server/initializers/migrations/0370-thumbnail.ts new file mode 100644 index 000000000..384ca1a15 --- /dev/null +++ b/server/initializers/migrations/0370-thumbnail.ts | |||
@@ -0,0 +1,50 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const query = ` | ||
11 | CREATE TABLE IF NOT EXISTS "thumbnail" | ||
12 | ( | ||
13 | "id" SERIAL, | ||
14 | "filename" VARCHAR(255) NOT NULL, | ||
15 | "height" INTEGER DEFAULT NULL, | ||
16 | "width" INTEGER DEFAULT NULL, | ||
17 | "type" INTEGER NOT NULL, | ||
18 | "fileUrl" VARCHAR(255), | ||
19 | "videoId" INTEGER REFERENCES "video" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
20 | "videoPlaylistId" INTEGER REFERENCES "videoPlaylist" ("id") ON DELETE CASCADE ON UPDATE CASCADE, | ||
21 | "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, | ||
22 | "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL, | ||
23 | PRIMARY KEY ("id") | ||
24 | );` | ||
25 | await utils.sequelize.query(query) | ||
26 | } | ||
27 | |||
28 | { | ||
29 | // All video thumbnails | ||
30 | const query = 'INSERT INTO "thumbnail" ("filename", "type", "videoId", "height", "width", "createdAt", "updatedAt")' + | ||
31 | 'SELECT uuid || \'.jpg\', 1, id, 110, 200, NOW(), NOW() FROM "video"' | ||
32 | await utils.sequelize.query(query) | ||
33 | } | ||
34 | |||
35 | { | ||
36 | // All video previews | ||
37 | const query = 'INSERT INTO "thumbnail" ("filename", "type", "videoId", "height", "width", "createdAt", "updatedAt")' + | ||
38 | 'SELECT uuid || \'.jpg\', 2, id, 315, 560, NOW(), NOW() FROM "video"' | ||
39 | await utils.sequelize.query(query) | ||
40 | } | ||
41 | } | ||
42 | |||
43 | function down (options) { | ||
44 | throw new Error('Not implemented.') | ||
45 | } | ||
46 | |||
47 | export { | ||
48 | up, | ||
49 | down | ||
50 | } | ||
diff --git a/server/initializers/migrations/0375-account-description.ts b/server/initializers/migrations/0375-account-description.ts new file mode 100644 index 000000000..1258563fd --- /dev/null +++ b/server/initializers/migrations/0375-account-description.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const data = { | ||
10 | type: Sequelize.STRING(1000), | ||
11 | allowNull: true, | ||
12 | defaultValue: null | ||
13 | } | ||
14 | |||
15 | await utils.queryInterface.changeColumn('account', 'description', data) | ||
16 | } | ||
17 | |||
18 | function down (options) { | ||
19 | throw new Error('Not implemented.') | ||
20 | } | ||
21 | |||
22 | export { | ||
23 | up, | ||
24 | down | ||
25 | } | ||
diff --git a/server/initializers/migrator.ts b/server/initializers/migrator.ts index adc2f9fb3..1cb0116b7 100644 --- a/server/initializers/migrator.ts +++ b/server/initializers/migrator.ts | |||
@@ -3,6 +3,7 @@ import { logger } from '../helpers/logger' | |||
3 | import { LAST_MIGRATION_VERSION } from './constants' | 3 | import { LAST_MIGRATION_VERSION } from './constants' |
4 | import { sequelizeTypescript } from './database' | 4 | import { sequelizeTypescript } from './database' |
5 | import { readdir } from 'fs-extra' | 5 | import { readdir } from 'fs-extra' |
6 | import { QueryTypes } from 'sequelize' | ||
6 | 7 | ||
7 | async function migrate () { | 8 | async function migrate () { |
8 | const tables = await sequelizeTypescript.getQueryInterface().showAllTables() | 9 | const tables = await sequelizeTypescript.getQueryInterface().showAllTables() |
@@ -13,7 +14,12 @@ async function migrate () { | |||
13 | 14 | ||
14 | let actualVersion: number | null = null | 15 | let actualVersion: number | null = null |
15 | 16 | ||
16 | const [ rows ] = await sequelizeTypescript.query('SELECT "migrationVersion" FROM "application"') | 17 | const query = 'SELECT "migrationVersion" FROM "application"' |
18 | const options = { | ||
19 | type: QueryTypes.SELECT as QueryTypes.SELECT | ||
20 | } | ||
21 | |||
22 | const rows = await sequelizeTypescript.query<{ migrationVersion: number }>(query, options) | ||
17 | if (rows && rows[0] && rows[0].migrationVersion) { | 23 | if (rows && rows[0] && rows[0].migrationVersion) { |
18 | actualVersion = rows[0].migrationVersion | 24 | actualVersion = rows[0].migrationVersion |
19 | } | 25 | } |
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 8215840da..25cd40905 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -12,7 +12,7 @@ import { logger } from '../../helpers/logger' | |||
12 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' | 12 | import { createPrivateAndPublicKeys } from '../../helpers/peertube-crypto' |
13 | import { doRequest, downloadImage } from '../../helpers/requests' | 13 | import { doRequest, downloadImage } from '../../helpers/requests' |
14 | import { getUrlFromWebfinger } from '../../helpers/webfinger' | 14 | import { getUrlFromWebfinger } from '../../helpers/webfinger' |
15 | import { AVATARS_SIZE, CONFIG, MIMETYPES, sequelizeTypescript } from '../../initializers' | 15 | import { AVATARS_SIZE, MIMETYPES, WEBSERVER } from '../../initializers/constants' |
16 | import { AccountModel } from '../../models/account/account' | 16 | import { AccountModel } from '../../models/account/account' |
17 | import { ActorModel } from '../../models/activitypub/actor' | 17 | import { ActorModel } from '../../models/activitypub/actor' |
18 | import { AvatarModel } from '../../models/avatar/avatar' | 18 | import { AvatarModel } from '../../models/avatar/avatar' |
@@ -21,6 +21,8 @@ import { VideoChannelModel } from '../../models/video/video-channel' | |||
21 | import { JobQueue } from '../job-queue' | 21 | import { JobQueue } from '../job-queue' |
22 | import { getServerActor } from '../../helpers/utils' | 22 | import { getServerActor } from '../../helpers/utils' |
23 | import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' | 23 | import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' |
24 | import { CONFIG } from '../../initializers/config' | ||
25 | import { sequelizeTypescript } from '../../initializers/database' | ||
24 | 26 | ||
25 | // Set account keys, this could be long so process after the account creation and do not block the client | 27 | // Set account keys, this could be long so process after the account creation and do not block the client |
26 | function setAsyncActorKeys (actor: ActorModel) { | 28 | function setAsyncActorKeys (actor: ActorModel) { |
@@ -44,6 +46,7 @@ async function getOrCreateActorAndServerAndModel ( | |||
44 | ) { | 46 | ) { |
45 | const actorUrl = getAPId(activityActor) | 47 | const actorUrl = getAPId(activityActor) |
46 | let created = false | 48 | let created = false |
49 | let accountPlaylistsUrl: string | ||
47 | 50 | ||
48 | let actor = await fetchActorByUrl(actorUrl, fetchType) | 51 | let actor = await fetchActorByUrl(actorUrl, fetchType) |
49 | // Orphan actor (not associated to an account of channel) so recreate it | 52 | // Orphan actor (not associated to an account of channel) so recreate it |
@@ -70,7 +73,8 @@ async function getOrCreateActorAndServerAndModel ( | |||
70 | 73 | ||
71 | try { | 74 | try { |
72 | // Don't recurse another time | 75 | // Don't recurse another time |
73 | ownerActor = await getOrCreateActorAndServerAndModel(accountAttributedTo.id, 'all', false) | 76 | const recurseIfNeeded = false |
77 | ownerActor = await getOrCreateActorAndServerAndModel(accountAttributedTo.id, 'all', recurseIfNeeded) | ||
74 | } catch (err) { | 78 | } catch (err) { |
75 | logger.error('Cannot get or create account attributed to video channel ' + actor.url) | 79 | logger.error('Cannot get or create account attributed to video channel ' + actor.url) |
76 | throw new Error(err) | 80 | throw new Error(err) |
@@ -79,6 +83,7 @@ async function getOrCreateActorAndServerAndModel ( | |||
79 | 83 | ||
80 | actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, result, ownerActor) | 84 | actor = await retryTransactionWrapper(saveActorAndServerAndModelIfNotExist, result, ownerActor) |
81 | created = true | 85 | created = true |
86 | accountPlaylistsUrl = result.playlists | ||
82 | } | 87 | } |
83 | 88 | ||
84 | if (actor.Account) actor.Account.Actor = actor | 89 | if (actor.Account) actor.Account.Actor = actor |
@@ -92,6 +97,12 @@ async function getOrCreateActorAndServerAndModel ( | |||
92 | await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) | 97 | await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) |
93 | } | 98 | } |
94 | 99 | ||
100 | // We created a new account: fetch the playlists | ||
101 | if (created === true && actor.Account && accountPlaylistsUrl) { | ||
102 | const payload = { uri: accountPlaylistsUrl, accountId: actor.Account.id, type: 'account-playlists' as 'account-playlists' } | ||
103 | await JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) | ||
104 | } | ||
105 | |||
95 | return actorRefreshed | 106 | return actorRefreshed |
96 | } | 107 | } |
97 | 108 | ||
@@ -107,7 +118,7 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU | |||
107 | followingCount: 0, | 118 | followingCount: 0, |
108 | inboxUrl: url + '/inbox', | 119 | inboxUrl: url + '/inbox', |
109 | outboxUrl: url + '/outbox', | 120 | outboxUrl: url + '/outbox', |
110 | sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox', | 121 | sharedInboxUrl: WEBSERVER.URL + '/inbox', |
111 | followersUrl: url + '/followers', | 122 | followersUrl: url + '/followers', |
112 | followingUrl: url + '/following' | 123 | followingUrl: url + '/following' |
113 | }) | 124 | }) |
@@ -259,7 +270,7 @@ async function refreshActorIfNeeded ( | |||
259 | return { refreshed: true, actor } | 270 | return { refreshed: true, actor } |
260 | }) | 271 | }) |
261 | } catch (err) { | 272 | } catch (err) { |
262 | logger.warn('Cannot refresh actor.', { err }) | 273 | logger.warn('Cannot refresh actor %s.', actor.url, { err }) |
263 | return { actor, refreshed: false } | 274 | return { actor, refreshed: false } |
264 | } | 275 | } |
265 | } | 276 | } |
@@ -333,6 +344,8 @@ function saveActorAndServerAndModelIfNotExist ( | |||
333 | actorCreated.VideoChannel.Account = ownerActor.Account | 344 | actorCreated.VideoChannel.Account = ownerActor.Account |
334 | } | 345 | } |
335 | 346 | ||
347 | actorCreated.Server = server | ||
348 | |||
336 | return actorCreated | 349 | return actorCreated |
337 | } | 350 | } |
338 | } | 351 | } |
@@ -342,6 +355,7 @@ type FetchRemoteActorResult = { | |||
342 | name: string | 355 | name: string |
343 | summary: string | 356 | summary: string |
344 | support?: string | 357 | support?: string |
358 | playlists?: string | ||
345 | avatarName?: string | 359 | avatarName?: string |
346 | attributedTo: ActivityPubAttributedTo[] | 360 | attributedTo: ActivityPubAttributedTo[] |
347 | } | 361 | } |
@@ -355,17 +369,18 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe | |||
355 | 369 | ||
356 | logger.info('Fetching remote actor %s.', actorUrl) | 370 | logger.info('Fetching remote actor %s.', actorUrl) |
357 | 371 | ||
358 | const requestResult = await doRequest(options) | 372 | const requestResult = await doRequest<ActivityPubActor>(options) |
359 | normalizeActor(requestResult.body) | 373 | normalizeActor(requestResult.body) |
360 | 374 | ||
361 | const actorJSON: ActivityPubActor = requestResult.body | 375 | const actorJSON = requestResult.body |
362 | if (isActorObjectValid(actorJSON) === false) { | 376 | if (isActorObjectValid(actorJSON) === false) { |
363 | logger.debug('Remote actor JSON is not valid.', { actorJSON }) | 377 | logger.debug('Remote actor JSON is not valid.', { actorJSON }) |
364 | return { result: undefined, statusCode: requestResult.response.statusCode } | 378 | return { result: undefined, statusCode: requestResult.response.statusCode } |
365 | } | 379 | } |
366 | 380 | ||
367 | if (checkUrlsSameHost(actorJSON.id, actorUrl) !== true) { | 381 | if (checkUrlsSameHost(actorJSON.id, actorUrl) !== true) { |
368 | throw new Error('Actor url ' + actorUrl + ' has not the same host than its AP id ' + actorJSON.id) | 382 | logger.warn('Actor url %s has not the same host than its AP id %s', actorUrl, actorJSON.id) |
383 | return { result: undefined, statusCode: requestResult.response.statusCode } | ||
369 | } | 384 | } |
370 | 385 | ||
371 | const followersCount = await fetchActorTotalItems(actorJSON.followers) | 386 | const followersCount = await fetchActorTotalItems(actorJSON.followers) |
@@ -398,6 +413,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe | |||
398 | avatarName, | 413 | avatarName, |
399 | summary: actorJSON.summary, | 414 | summary: actorJSON.summary, |
400 | support: actorJSON.support, | 415 | support: actorJSON.support, |
416 | playlists: actorJSON.playlists, | ||
401 | attributedTo: actorJSON.attributedTo | 417 | attributedTo: actorJSON.attributedTo |
402 | } | 418 | } |
403 | } | 419 | } |
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index 10277eca7..771a01366 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience } from '../../../shared/models/activitypub' | 2 | import { ActivityAudience } from '../../../shared/models/activitypub' |
3 | import { ACTIVITY_PUB } from '../../initializers' | 3 | import { ACTIVITY_PUB } from '../../initializers/constants' |
4 | import { ActorModel } from '../../models/activitypub/actor' | 4 | import { ActorModel } from '../../models/activitypub/actor' |
5 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
6 | import { VideoCommentModel } from '../../models/video/video-comment' | 6 | import { VideoCommentModel } from '../../models/video/video-comment' |
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts index f6f068b45..de5cc54ac 100644 --- a/server/lib/activitypub/cache-file.ts +++ b/server/lib/activitypub/cache-file.ts | |||
@@ -2,10 +2,27 @@ import { CacheFileObject } from '../../../shared/index' | |||
2 | import { VideoModel } from '../../models/video/video' | 2 | import { VideoModel } from '../../models/video/video' |
3 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 3 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
4 | import { Transaction } from 'sequelize' | 4 | import { Transaction } from 'sequelize' |
5 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | ||
5 | 6 | ||
6 | function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) { | 7 | function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) { |
7 | const url = cacheFileObject.url | ||
8 | 8 | ||
9 | if (cacheFileObject.url.mediaType === 'application/x-mpegURL') { | ||
10 | const url = cacheFileObject.url | ||
11 | |||
12 | const playlist = video.VideoStreamingPlaylists.find(t => t.type === VideoStreamingPlaylistType.HLS) | ||
13 | if (!playlist) throw new Error('Cannot find HLS playlist of video ' + video.url) | ||
14 | |||
15 | return { | ||
16 | expiresOn: new Date(cacheFileObject.expires), | ||
17 | url: cacheFileObject.id, | ||
18 | fileUrl: url.href, | ||
19 | strategy: null, | ||
20 | videoStreamingPlaylistId: playlist.id, | ||
21 | actorId: byActor.id | ||
22 | } | ||
23 | } | ||
24 | |||
25 | const url = cacheFileObject.url | ||
9 | const videoFile = video.VideoFiles.find(f => { | 26 | const videoFile = video.VideoFiles.find(f => { |
10 | return f.resolution === url.height && f.fps === url.fps | 27 | return f.resolution === url.height && f.fps === url.fps |
11 | }) | 28 | }) |
@@ -15,7 +32,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject | |||
15 | return { | 32 | return { |
16 | expiresOn: new Date(cacheFileObject.expires), | 33 | expiresOn: new Date(cacheFileObject.expires), |
17 | url: cacheFileObject.id, | 34 | url: cacheFileObject.id, |
18 | fileUrl: cacheFileObject.url.href, | 35 | fileUrl: url.href, |
19 | strategy: null, | 36 | strategy: null, |
20 | videoFileId: videoFile.id, | 37 | videoFileId: videoFile.id, |
21 | actorId: byActor.id | 38 | actorId: byActor.id |
@@ -51,8 +68,8 @@ function updateCacheFile ( | |||
51 | 68 | ||
52 | const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) | 69 | const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) |
53 | 70 | ||
54 | redundancyModel.set('expires', attributes.expiresOn) | 71 | redundancyModel.expiresOn = attributes.expiresOn |
55 | redundancyModel.set('fileUrl', attributes.fileUrl) | 72 | redundancyModel.fileUrl = attributes.fileUrl |
56 | 73 | ||
57 | return redundancyModel.save({ transaction: t }) | 74 | return redundancyModel.save({ transaction: t }) |
58 | } | 75 | } |
diff --git a/server/lib/activitypub/crawl.ts b/server/lib/activitypub/crawl.ts index 1b9b14c2e..686eef04d 100644 --- a/server/lib/activitypub/crawl.ts +++ b/server/lib/activitypub/crawl.ts | |||
@@ -1,10 +1,14 @@ | |||
1 | import { ACTIVITY_PUB, JOB_REQUEST_TIMEOUT } from '../../initializers' | 1 | import { ACTIVITY_PUB, JOB_REQUEST_TIMEOUT, WEBSERVER } from '../../initializers/constants' |
2 | import { doRequest } from '../../helpers/requests' | 2 | import { doRequest } from '../../helpers/requests' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import * as Bluebird from 'bluebird' | 4 | import * as Bluebird from 'bluebird' |
5 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' | 5 | import { ActivityPubOrderedCollection } from '../../../shared/models/activitypub' |
6 | import { parse } from 'url' | ||
6 | 7 | ||
7 | async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Promise<any> | Bluebird<any>) { | 8 | type HandlerFunction<T> = (items: T[]) => (Promise<any> | Bluebird<any>) |
9 | type CleanerFunction = (startedDate: Date) => (Promise<any> | Bluebird<any>) | ||
10 | |||
11 | async function crawlCollectionPage <T> (uri: string, handler: HandlerFunction<T>, cleaner?: CleanerFunction) { | ||
8 | logger.info('Crawling ActivityPub data on %s.', uri) | 12 | logger.info('Crawling ActivityPub data on %s.', uri) |
9 | 13 | ||
10 | const options = { | 14 | const options = { |
@@ -15,6 +19,8 @@ async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Pr | |||
15 | timeout: JOB_REQUEST_TIMEOUT | 19 | timeout: JOB_REQUEST_TIMEOUT |
16 | } | 20 | } |
17 | 21 | ||
22 | const startDate = new Date() | ||
23 | |||
18 | const response = await doRequest<ActivityPubOrderedCollection<T>>(options) | 24 | const response = await doRequest<ActivityPubOrderedCollection<T>>(options) |
19 | const firstBody = response.body | 25 | const firstBody = response.body |
20 | 26 | ||
@@ -22,6 +28,10 @@ async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Pr | |||
22 | let i = 0 | 28 | let i = 0 |
23 | let nextLink = firstBody.first | 29 | let nextLink = firstBody.first |
24 | while (nextLink && i < limit) { | 30 | while (nextLink && i < limit) { |
31 | // Don't crawl ourselves | ||
32 | const remoteHost = parse(nextLink).host | ||
33 | if (remoteHost === WEBSERVER.HOST) continue | ||
34 | |||
25 | options.uri = nextLink | 35 | options.uri = nextLink |
26 | 36 | ||
27 | const { body } = await doRequest<ActivityPubOrderedCollection<T>>(options) | 37 | const { body } = await doRequest<ActivityPubOrderedCollection<T>>(options) |
@@ -35,6 +45,8 @@ async function crawlCollectionPage <T> (uri: string, handler: (items: T[]) => Pr | |||
35 | await handler(items) | 45 | await handler(items) |
36 | } | 46 | } |
37 | } | 47 | } |
48 | |||
49 | if (cleaner) await cleaner(startDate) | ||
38 | } | 50 | } |
39 | 51 | ||
40 | export { | 52 | export { |
diff --git a/server/lib/activitypub/index.ts b/server/lib/activitypub/index.ts index 6906bf9d3..d8c7d83b7 100644 --- a/server/lib/activitypub/index.ts +++ b/server/lib/activitypub/index.ts | |||
@@ -2,6 +2,7 @@ export * from './process' | |||
2 | export * from './send' | 2 | export * from './send' |
3 | export * from './actor' | 3 | export * from './actor' |
4 | export * from './share' | 4 | export * from './share' |
5 | export * from './playlist' | ||
5 | export * from './videos' | 6 | export * from './videos' |
6 | export * from './video-comments' | 7 | export * from './video-comments' |
7 | export * from './video-rates' | 8 | export * from './video-rates' |
diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts new file mode 100644 index 000000000..36a91faec --- /dev/null +++ b/server/lib/activitypub/playlist.ts | |||
@@ -0,0 +1,213 @@ | |||
1 | import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' | ||
2 | import { crawlCollectionPage } from './crawl' | ||
3 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | ||
4 | import { AccountModel } from '../../models/account/account' | ||
5 | import { isArray } from '../../helpers/custom-validators/misc' | ||
6 | import { getOrCreateActorAndServerAndModel } from './actor' | ||
7 | import { logger } from '../../helpers/logger' | ||
8 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
9 | import { doRequest } from '../../helpers/requests' | ||
10 | import { checkUrlsSameHost } from '../../helpers/activitypub' | ||
11 | import * as Bluebird from 'bluebird' | ||
12 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' | ||
13 | import { getOrCreateVideoAndAccountAndChannel } from './videos' | ||
14 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' | ||
15 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | ||
16 | import { VideoModel } from '../../models/video/video' | ||
17 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
18 | import { sequelizeTypescript } from '../../initializers/database' | ||
19 | import { createPlaylistMiniatureFromUrl } from '../thumbnail' | ||
20 | import { FilteredModelAttributes } from '../../typings/sequelize' | ||
21 | |||
22 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) { | ||
23 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED | ||
24 | |||
25 | return { | ||
26 | name: playlistObject.name, | ||
27 | description: playlistObject.content, | ||
28 | privacy, | ||
29 | url: playlistObject.id, | ||
30 | uuid: playlistObject.uuid, | ||
31 | ownerAccountId: byAccount.id, | ||
32 | videoChannelId: null, | ||
33 | createdAt: new Date(playlistObject.published), | ||
34 | updatedAt: new Date(playlistObject.updated) | ||
35 | } | ||
36 | } | ||
37 | |||
38 | function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: VideoPlaylistModel, video: VideoModel) { | ||
39 | return { | ||
40 | position: elementObject.position, | ||
41 | url: elementObject.id, | ||
42 | startTimestamp: elementObject.startTimestamp || null, | ||
43 | stopTimestamp: elementObject.stopTimestamp || null, | ||
44 | videoPlaylistId: videoPlaylist.id, | ||
45 | videoId: video.id | ||
46 | } | ||
47 | } | ||
48 | |||
49 | async function createAccountPlaylists (playlistUrls: string[], account: AccountModel) { | ||
50 | await Bluebird.map(playlistUrls, async playlistUrl => { | ||
51 | try { | ||
52 | const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl) | ||
53 | if (exists === true) return | ||
54 | |||
55 | // Fetch url | ||
56 | const { body } = await doRequest<PlaylistObject>({ | ||
57 | uri: playlistUrl, | ||
58 | json: true, | ||
59 | activityPub: true | ||
60 | }) | ||
61 | |||
62 | if (!isPlaylistObjectValid(body)) { | ||
63 | throw new Error(`Invalid playlist object when fetch account playlists: ${JSON.stringify(body)}`) | ||
64 | } | ||
65 | |||
66 | if (!isArray(body.to)) { | ||
67 | throw new Error('Playlist does not have an audience.') | ||
68 | } | ||
69 | |||
70 | return createOrUpdateVideoPlaylist(body, account, body.to) | ||
71 | } catch (err) { | ||
72 | logger.warn('Cannot add playlist element %s.', playlistUrl, { err }) | ||
73 | } | ||
74 | }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) | ||
75 | } | ||
76 | |||
77 | async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModel, to: string[]) { | ||
78 | const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) | ||
79 | |||
80 | if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { | ||
81 | const actor = await getOrCreateActorAndServerAndModel(playlistObject.attributedTo[0]) | ||
82 | |||
83 | if (actor.VideoChannel) { | ||
84 | playlistAttributes.videoChannelId = actor.VideoChannel.id | ||
85 | } else { | ||
86 | logger.warn('Attributed to of video playlist %s is not a video channel.', playlistObject.id, { playlistObject }) | ||
87 | } | ||
88 | } | ||
89 | |||
90 | const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) | ||
91 | |||
92 | let accItems: string[] = [] | ||
93 | await crawlCollectionPage<string>(playlistObject.id, items => { | ||
94 | accItems = accItems.concat(items) | ||
95 | |||
96 | return Promise.resolve() | ||
97 | }) | ||
98 | |||
99 | const refreshedPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(playlist.id, null) | ||
100 | |||
101 | if (playlistObject.icon) { | ||
102 | try { | ||
103 | const thumbnailModel = await createPlaylistMiniatureFromUrl(playlistObject.icon.url, refreshedPlaylist) | ||
104 | await refreshedPlaylist.setAndSaveThumbnail(thumbnailModel, undefined) | ||
105 | } catch (err) { | ||
106 | logger.warn('Cannot generate thumbnail of %s.', playlistObject.id, { err }) | ||
107 | } | ||
108 | } | ||
109 | |||
110 | return resetVideoPlaylistElements(accItems, refreshedPlaylist) | ||
111 | } | ||
112 | |||
113 | async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise<VideoPlaylistModel> { | ||
114 | if (!videoPlaylist.isOutdated()) return videoPlaylist | ||
115 | |||
116 | try { | ||
117 | const { statusCode, playlistObject } = await fetchRemoteVideoPlaylist(videoPlaylist.url) | ||
118 | if (statusCode === 404) { | ||
119 | logger.info('Cannot refresh remote video playlist %s: it does not exist anymore. Deleting it.', videoPlaylist.url) | ||
120 | |||
121 | await videoPlaylist.destroy() | ||
122 | return undefined | ||
123 | } | ||
124 | |||
125 | if (playlistObject === undefined) { | ||
126 | logger.warn('Cannot refresh remote playlist %s: invalid body.', videoPlaylist.url) | ||
127 | |||
128 | await videoPlaylist.setAsRefreshed() | ||
129 | return videoPlaylist | ||
130 | } | ||
131 | |||
132 | const byAccount = videoPlaylist.OwnerAccount | ||
133 | await createOrUpdateVideoPlaylist(playlistObject, byAccount, playlistObject.to) | ||
134 | |||
135 | return videoPlaylist | ||
136 | } catch (err) { | ||
137 | logger.warn('Cannot refresh video playlist %s.', videoPlaylist.url, { err }) | ||
138 | |||
139 | await videoPlaylist.setAsRefreshed() | ||
140 | return videoPlaylist | ||
141 | } | ||
142 | } | ||
143 | |||
144 | // --------------------------------------------------------------------------- | ||
145 | |||
146 | export { | ||
147 | createAccountPlaylists, | ||
148 | playlistObjectToDBAttributes, | ||
149 | playlistElementObjectToDBAttributes, | ||
150 | createOrUpdateVideoPlaylist, | ||
151 | refreshVideoPlaylistIfNeeded | ||
152 | } | ||
153 | |||
154 | // --------------------------------------------------------------------------- | ||
155 | |||
156 | async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) { | ||
157 | const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = [] | ||
158 | |||
159 | await Bluebird.map(elementUrls, async elementUrl => { | ||
160 | try { | ||
161 | // Fetch url | ||
162 | const { body } = await doRequest<PlaylistElementObject>({ | ||
163 | uri: elementUrl, | ||
164 | json: true, | ||
165 | activityPub: true | ||
166 | }) | ||
167 | |||
168 | if (!isPlaylistElementObjectValid(body)) throw new Error(`Invalid body in video get playlist element ${elementUrl}`) | ||
169 | |||
170 | if (checkUrlsSameHost(body.id, elementUrl) !== true) { | ||
171 | throw new Error(`Playlist element url ${elementUrl} host is different from the AP object id ${body.id}`) | ||
172 | } | ||
173 | |||
174 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: { id: body.url }, fetchType: 'only-video' }) | ||
175 | |||
176 | elementsToCreate.push(playlistElementObjectToDBAttributes(body, playlist, video)) | ||
177 | } catch (err) { | ||
178 | logger.warn('Cannot add playlist element %s.', elementUrl, { err }) | ||
179 | } | ||
180 | }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) | ||
181 | |||
182 | await sequelizeTypescript.transaction(async t => { | ||
183 | await VideoPlaylistElementModel.deleteAllOf(playlist.id, t) | ||
184 | |||
185 | for (const element of elementsToCreate) { | ||
186 | await VideoPlaylistElementModel.create(element, { transaction: t }) | ||
187 | } | ||
188 | }) | ||
189 | |||
190 | logger.info('Reset playlist %s with %s elements.', playlist.url, elementsToCreate.length) | ||
191 | |||
192 | return undefined | ||
193 | } | ||
194 | |||
195 | async function fetchRemoteVideoPlaylist (playlistUrl: string): Promise<{ statusCode: number, playlistObject: PlaylistObject }> { | ||
196 | const options = { | ||
197 | uri: playlistUrl, | ||
198 | method: 'GET', | ||
199 | json: true, | ||
200 | activityPub: true | ||
201 | } | ||
202 | |||
203 | logger.info('Fetching remote playlist %s.', playlistUrl) | ||
204 | |||
205 | const { response, body } = await doRequest(options) | ||
206 | |||
207 | if (isPlaylistObjectValid(body) === false || checkUrlsSameHost(body.id, playlistUrl) !== true) { | ||
208 | logger.debug('Remote video playlist JSON is not valid.', { body }) | ||
209 | return { statusCode: response.statusCode, playlistObject: undefined } | ||
210 | } | ||
211 | |||
212 | return { statusCode: response.statusCode, playlistObject: body } | ||
213 | } | ||
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 5f4d793a5..e882669ce 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -12,6 +12,8 @@ import { Notifier } from '../../notifier' | |||
12 | import { processViewActivity } from './process-view' | 12 | import { processViewActivity } from './process-view' |
13 | import { processDislikeActivity } from './process-dislike' | 13 | import { processDislikeActivity } from './process-dislike' |
14 | import { processFlagActivity } from './process-flag' | 14 | import { processFlagActivity } from './process-flag' |
15 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | ||
16 | import { createOrUpdateVideoPlaylist } from '../playlist' | ||
15 | 17 | ||
16 | async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { | 18 | async function processCreateActivity (activity: ActivityCreate, byActor: ActorModel) { |
17 | const activityObject = activity.object | 19 | const activityObject = activity.object |
@@ -38,7 +40,11 @@ async function processCreateActivity (activity: ActivityCreate, byActor: ActorMo | |||
38 | } | 40 | } |
39 | 41 | ||
40 | if (activityType === 'CacheFile') { | 42 | if (activityType === 'CacheFile') { |
41 | return retryTransactionWrapper(processCacheFile, activity, byActor) | 43 | return retryTransactionWrapper(processCreateCacheFile, activity, byActor) |
44 | } | ||
45 | |||
46 | if (activityType === 'Playlist') { | ||
47 | return retryTransactionWrapper(processCreatePlaylist, activity, byActor) | ||
42 | } | 48 | } |
43 | 49 | ||
44 | logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) | 50 | logger.warn('Unknown activity object type %s when creating activity.', activityType, { activity: activity.id }) |
@@ -63,7 +69,7 @@ async function processCreateVideo (activity: ActivityCreate) { | |||
63 | return video | 69 | return video |
64 | } | 70 | } |
65 | 71 | ||
66 | async function processCacheFile (activity: ActivityCreate, byActor: ActorModel) { | 72 | async function processCreateCacheFile (activity: ActivityCreate, byActor: ActorModel) { |
67 | const cacheFile = activity.object as CacheFileObject | 73 | const cacheFile = activity.object as CacheFileObject |
68 | 74 | ||
69 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) | 75 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) |
@@ -98,3 +104,12 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Act | |||
98 | 104 | ||
99 | if (created === true) Notifier.Instance.notifyOnNewComment(comment) | 105 | if (created === true) Notifier.Instance.notifyOnNewComment(comment) |
100 | } | 106 | } |
107 | |||
108 | async function processCreatePlaylist (activity: ActivityCreate, byActor: ActorModel) { | ||
109 | const playlistObject = activity.object as PlaylistObject | ||
110 | const byAccount = byActor.Account | ||
111 | |||
112 | if (!byAccount) throw new Error('Cannot create video playlist with the non account actor ' + byActor.url) | ||
113 | |||
114 | await createOrUpdateVideoPlaylist(playlistObject, byAccount, activity.to) | ||
115 | } | ||
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index 155d2ffcc..76f07fd8a 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts | |||
@@ -8,6 +8,7 @@ import { VideoModel } from '../../../models/video/video' | |||
8 | import { VideoChannelModel } from '../../../models/video/video-channel' | 8 | import { VideoChannelModel } from '../../../models/video/video-channel' |
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | 9 | import { VideoCommentModel } from '../../../models/video/video-comment' |
10 | import { forwardVideoRelatedActivity } from '../send/utils' | 10 | import { forwardVideoRelatedActivity } from '../send/utils' |
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
11 | 12 | ||
12 | async function processDeleteActivity (activity: ActivityDelete, byActor: ActorModel) { | 13 | async function processDeleteActivity (activity: ActivityDelete, byActor: ActorModel) { |
13 | const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id | 14 | const objectUrl = typeof activity.object === 'string' ? activity.object : activity.object.id |
@@ -45,6 +46,15 @@ async function processDeleteActivity (activity: ActivityDelete, byActor: ActorMo | |||
45 | } | 46 | } |
46 | } | 47 | } |
47 | 48 | ||
49 | { | ||
50 | const videoPlaylist = await VideoPlaylistModel.loadByUrlAndPopulateAccount(objectUrl) | ||
51 | if (videoPlaylist) { | ||
52 | if (videoPlaylist.isOwned()) throw new Error(`Remote instance cannot delete owned playlist ${videoPlaylist.url}.`) | ||
53 | |||
54 | return retryTransactionWrapper(processDeleteVideoPlaylist, byActor, videoPlaylist) | ||
55 | } | ||
56 | } | ||
57 | |||
48 | return undefined | 58 | return undefined |
49 | } | 59 | } |
50 | 60 | ||
@@ -70,6 +80,20 @@ async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) | |||
70 | logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) | 80 | logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) |
71 | } | 81 | } |
72 | 82 | ||
83 | async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) { | ||
84 | logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid) | ||
85 | |||
86 | await sequelizeTypescript.transaction(async t => { | ||
87 | if (playlistToDelete.OwnerAccount.Actor.id !== actor.id) { | ||
88 | throw new Error('Account ' + actor.url + ' does not own video playlist ' + playlistToDelete.url) | ||
89 | } | ||
90 | |||
91 | await playlistToDelete.destroy({ transaction: t }) | ||
92 | }) | ||
93 | |||
94 | logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid) | ||
95 | } | ||
96 | |||
73 | async function processDeleteAccount (accountToRemove: AccountModel) { | 97 | async function processDeleteAccount (accountToRemove: AccountModel) { |
74 | logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid) | 98 | logger.debug('Removing remote account "%s".', accountToRemove.Actor.uuid) |
75 | 99 | ||
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index 0cd537187..ed16ba172 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts | |||
@@ -4,9 +4,11 @@ import { logger } from '../../../helpers/logger' | |||
4 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
6 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 6 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
7 | import { sendAccept } from '../send' | 7 | import { sendAccept, sendReject } from '../send' |
8 | import { Notifier } from '../../notifier' | 8 | import { Notifier } from '../../notifier' |
9 | import { getAPId } from '../../../helpers/activitypub' | 9 | import { getAPId } from '../../../helpers/activitypub' |
10 | import { getServerActor } from '../../../helpers/utils' | ||
11 | import { CONFIG } from '../../../initializers/config' | ||
10 | 12 | ||
11 | async function processFollowActivity (activity: ActivityFollow, byActor: ActorModel) { | 13 | async function processFollowActivity (activity: ActivityFollow, byActor: ActorModel) { |
12 | const activityObject = getAPId(activity.object) | 14 | const activityObject = getAPId(activity.object) |
@@ -23,12 +25,23 @@ export { | |||
23 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
24 | 26 | ||
25 | async function processFollow (actor: ActorModel, targetActorURL: string) { | 27 | async function processFollow (actor: ActorModel, targetActorURL: string) { |
26 | const { actorFollow, created } = await sequelizeTypescript.transaction(async t => { | 28 | const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => { |
27 | const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) | 29 | const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) |
28 | 30 | ||
29 | if (!targetActor) throw new Error('Unknown actor') | 31 | if (!targetActor) throw new Error('Unknown actor') |
30 | if (targetActor.isOwned() === false) throw new Error('This is not a local actor.') | 32 | if (targetActor.isOwned() === false) throw new Error('This is not a local actor.') |
31 | 33 | ||
34 | const serverActor = await getServerActor() | ||
35 | const isFollowingInstance = targetActor.id === serverActor.id | ||
36 | |||
37 | if (isFollowingInstance && CONFIG.FOLLOWERS.INSTANCE.ENABLED === false) { | ||
38 | logger.info('Rejecting %s because instance followers are disabled.', targetActor.url) | ||
39 | |||
40 | await sendReject(actor, targetActor) | ||
41 | |||
42 | return { actorFollow: undefined } | ||
43 | } | ||
44 | |||
32 | const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({ | 45 | const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({ |
33 | where: { | 46 | where: { |
34 | actorId: actor.id, | 47 | actorId: actor.id, |
@@ -37,15 +50,12 @@ async function processFollow (actor: ActorModel, targetActorURL: string) { | |||
37 | defaults: { | 50 | defaults: { |
38 | actorId: actor.id, | 51 | actorId: actor.id, |
39 | targetActorId: targetActor.id, | 52 | targetActorId: targetActor.id, |
40 | state: 'accepted' | 53 | state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' |
41 | }, | 54 | }, |
42 | transaction: t | 55 | transaction: t |
43 | }) | 56 | }) |
44 | 57 | ||
45 | actorFollow.ActorFollower = actor | 58 | if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { |
46 | actorFollow.ActorFollowing = targetActor | ||
47 | |||
48 | if (actorFollow.state !== 'accepted') { | ||
49 | actorFollow.state = 'accepted' | 59 | actorFollow.state = 'accepted' |
50 | await actorFollow.save({ transaction: t }) | 60 | await actorFollow.save({ transaction: t }) |
51 | } | 61 | } |
@@ -54,12 +64,18 @@ async function processFollow (actor: ActorModel, targetActorURL: string) { | |||
54 | actorFollow.ActorFollowing = targetActor | 64 | actorFollow.ActorFollowing = targetActor |
55 | 65 | ||
56 | // Target sends to actor he accepted the follow request | 66 | // Target sends to actor he accepted the follow request |
57 | await sendAccept(actorFollow) | 67 | if (actorFollow.state === 'accepted') await sendAccept(actorFollow) |
58 | 68 | ||
59 | return { actorFollow, created } | 69 | return { actorFollow, created, isFollowingInstance } |
60 | }) | 70 | }) |
61 | 71 | ||
62 | if (created) Notifier.Instance.notifyOfNewFollow(actorFollow) | 72 | // Rejected |
73 | if (!actorFollow) return | ||
74 | |||
75 | if (created) { | ||
76 | if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow) | ||
77 | else Notifier.Instance.notifyOfNewUserFollow(actorFollow) | ||
78 | } | ||
63 | 79 | ||
64 | logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url) | 80 | logger.info('Actor %s is followed by actor %s.', targetActorURL, actor.url) |
65 | } | 81 | } |
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index ed0177a67..2d48848fe 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts | |||
@@ -108,7 +108,10 @@ async function processUndoCacheFile (byActor: ActorModel, activity: ActivityUndo | |||
108 | 108 | ||
109 | return sequelizeTypescript.transaction(async t => { | 109 | return sequelizeTypescript.transaction(async t => { |
110 | const cacheFile = await VideoRedundancyModel.loadByUrl(cacheFileObject.id) | 110 | const cacheFile = await VideoRedundancyModel.loadByUrl(cacheFileObject.id) |
111 | if (!cacheFile) throw new Error('Unknown video cache ' + cacheFileObject.id) | 111 | if (!cacheFile) { |
112 | logger.debug('Cannot undo unknown video cache %s.', cacheFileObject.id) | ||
113 | return | ||
114 | } | ||
112 | 115 | ||
113 | if (cacheFile.actorId !== byActor.id) throw new Error('Cannot delete redundancy ' + cacheFile.url + ' of another actor.') | 116 | if (cacheFile.actorId !== byActor.id) throw new Error('Cannot delete redundancy ' + cacheFile.url + ' of another actor.') |
114 | 117 | ||
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index c6b42d846..54a9234bb 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -12,6 +12,8 @@ import { sanitizeAndCheckVideoTorrentObject } from '../../../helpers/custom-vali | |||
12 | import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' | 12 | import { isCacheFileObjectValid } from '../../../helpers/custom-validators/activitypub/cache-file' |
13 | import { createOrUpdateCacheFile } from '../cache-file' | 13 | import { createOrUpdateCacheFile } from '../cache-file' |
14 | import { forwardVideoRelatedActivity } from '../send/utils' | 14 | import { forwardVideoRelatedActivity } from '../send/utils' |
15 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | ||
16 | import { createOrUpdateVideoPlaylist } from '../playlist' | ||
15 | 17 | ||
16 | async function processUpdateActivity (activity: ActivityUpdate, byActor: ActorModel) { | 18 | async function processUpdateActivity (activity: ActivityUpdate, byActor: ActorModel) { |
17 | const objectType = activity.object.type | 19 | const objectType = activity.object.type |
@@ -32,6 +34,10 @@ async function processUpdateActivity (activity: ActivityUpdate, byActor: ActorMo | |||
32 | return retryTransactionWrapper(processUpdateCacheFile, byActorFull, activity) | 34 | return retryTransactionWrapper(processUpdateCacheFile, byActorFull, activity) |
33 | } | 35 | } |
34 | 36 | ||
37 | if (objectType === 'Playlist') { | ||
38 | return retryTransactionWrapper(processUpdatePlaylist, byActor, activity) | ||
39 | } | ||
40 | |||
35 | return undefined | 41 | return undefined |
36 | } | 42 | } |
37 | 43 | ||
@@ -114,9 +120,11 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) | |||
114 | 120 | ||
115 | await actor.save({ transaction: t }) | 121 | await actor.save({ transaction: t }) |
116 | 122 | ||
117 | accountOrChannelInstance.set('name', actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername) | 123 | accountOrChannelInstance.name = actorAttributesToUpdate.name || actorAttributesToUpdate.preferredUsername |
118 | accountOrChannelInstance.set('description', actorAttributesToUpdate.summary) | 124 | accountOrChannelInstance.description = actorAttributesToUpdate.summary |
119 | accountOrChannelInstance.set('support', actorAttributesToUpdate.support) | 125 | |
126 | if (accountOrChannelInstance instanceof VideoChannelModel) accountOrChannelInstance.support = actorAttributesToUpdate.support | ||
127 | |||
120 | await accountOrChannelInstance.save({ transaction: t }) | 128 | await accountOrChannelInstance.save({ transaction: t }) |
121 | }) | 129 | }) |
122 | 130 | ||
@@ -135,3 +143,12 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) | |||
135 | throw err | 143 | throw err |
136 | } | 144 | } |
137 | } | 145 | } |
146 | |||
147 | async function processUpdatePlaylist (byActor: ActorModel, activity: ActivityUpdate) { | ||
148 | const playlistObject = activity.object as PlaylistObject | ||
149 | const byAccount = byActor.Account | ||
150 | |||
151 | if (!byAccount) throw new Error('Cannot update video playlist with the non account actor ' + byActor.url) | ||
152 | |||
153 | await createOrUpdateVideoPlaylist(playlistObject, byAccount, activity.to) | ||
154 | } | ||
diff --git a/server/lib/activitypub/send/index.ts b/server/lib/activitypub/send/index.ts index 79ba6c7fe..028936810 100644 --- a/server/lib/activitypub/send/index.ts +++ b/server/lib/activitypub/send/index.ts | |||
@@ -1,8 +1,10 @@ | |||
1 | export * from './send-accept' | 1 | export * from './send-accept' |
2 | export * from './send-accept' | ||
2 | export * from './send-announce' | 3 | export * from './send-announce' |
3 | export * from './send-create' | 4 | export * from './send-create' |
4 | export * from './send-delete' | 5 | export * from './send-delete' |
5 | export * from './send-follow' | 6 | export * from './send-follow' |
6 | export * from './send-like' | 7 | export * from './send-like' |
8 | export * from './send-reject' | ||
7 | export * from './send-undo' | 9 | export * from './send-undo' |
8 | export * from './send-update' | 10 | export * from './send-update' |
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts index b6abde13d..388a9ed23 100644 --- a/server/lib/activitypub/send/send-accept.ts +++ b/server/lib/activitypub/send/send-accept.ts | |||
@@ -17,7 +17,7 @@ async function sendAccept (actorFollow: ActorFollowModel) { | |||
17 | 17 | ||
18 | logger.info('Creating job to accept follower %s.', follower.url) | 18 | logger.info('Creating job to accept follower %s.', follower.url) |
19 | 19 | ||
20 | const followUrl = getActorFollowActivityPubUrl(actorFollow) | 20 | const followUrl = getActorFollowActivityPubUrl(follower, me) |
21 | const followData = buildFollowActivity(followUrl, follower, me) | 21 | const followData = buildFollowActivity(followUrl, follower, me) |
22 | 22 | ||
23 | const url = getActorFollowAcceptActivityPubUrl(actorFollow) | 23 | const url = getActorFollowAcceptActivityPubUrl(actorFollow) |
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index e3fca0a17..28f18595b 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts | |||
@@ -3,13 +3,14 @@ import { ActivityAudience, ActivityCreate } from '../../../../shared/models/acti | |||
3 | import { VideoPrivacy } from '../../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { ActorModel } from '../../../models/activitypub/actor' | 4 | import { ActorModel } from '../../../models/activitypub/actor' |
5 | import { VideoModel } from '../../../models/video/video' | 5 | import { VideoModel } from '../../../models/video/video' |
6 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
7 | import { VideoCommentModel } from '../../../models/video/video-comment' | 6 | import { VideoCommentModel } from '../../../models/video/video-comment' |
8 | import { getVideoAbuseActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoViewActivityPubUrl } from '../url' | ||
9 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 7 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
10 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' | 8 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' |
11 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
12 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | 10 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' |
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
12 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
13 | import { getServerActor } from '../../../helpers/utils' | ||
13 | 14 | ||
14 | async function sendCreateVideo (video: VideoModel, t: Transaction) { | 15 | async function sendCreateVideo (video: VideoModel, t: Transaction) { |
15 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | 16 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined |
@@ -25,34 +26,36 @@ async function sendCreateVideo (video: VideoModel, t: Transaction) { | |||
25 | return broadcastToFollowers(createActivity, byActor, [ byActor ], t) | 26 | return broadcastToFollowers(createActivity, byActor, [ byActor ], t) |
26 | } | 27 | } |
27 | 28 | ||
28 | async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel) { | 29 | async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, fileRedundancy: VideoRedundancyModel) { |
29 | if (!video.VideoChannel.Account.Actor.serverId) return // Local | ||
30 | |||
31 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | ||
32 | |||
33 | logger.info('Creating job to send video abuse %s.', url) | ||
34 | |||
35 | // Custom audience, we only send the abuse to the origin instance | ||
36 | const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } | ||
37 | const createActivity = buildCreateActivity(url, byActor, videoAbuse.toActivityPubObject(), audience) | ||
38 | |||
39 | return unicastTo(createActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) | ||
40 | } | ||
41 | |||
42 | async function sendCreateCacheFile (byActor: ActorModel, fileRedundancy: VideoRedundancyModel) { | ||
43 | logger.info('Creating job to send file cache of %s.', fileRedundancy.url) | 30 | logger.info('Creating job to send file cache of %s.', fileRedundancy.url) |
44 | 31 | ||
45 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(fileRedundancy.VideoFile.Video.id) | ||
46 | const redundancyObject = fileRedundancy.toActivityPubObject() | ||
47 | |||
48 | return sendVideoRelatedCreateActivity({ | 32 | return sendVideoRelatedCreateActivity({ |
49 | byActor, | 33 | byActor, |
50 | video, | 34 | video, |
51 | url: fileRedundancy.url, | 35 | url: fileRedundancy.url, |
52 | object: redundancyObject | 36 | object: fileRedundancy.toActivityPubObject() |
53 | }) | 37 | }) |
54 | } | 38 | } |
55 | 39 | ||
40 | async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transaction) { | ||
41 | if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined | ||
42 | |||
43 | logger.info('Creating job to send create video playlist of %s.', playlist.url) | ||
44 | |||
45 | const byActor = playlist.OwnerAccount.Actor | ||
46 | const audience = getAudience(byActor, playlist.privacy === VideoPlaylistPrivacy.PUBLIC) | ||
47 | |||
48 | const object = await playlist.toActivityPubObject(null, t) | ||
49 | const createActivity = buildCreateActivity(playlist.url, byActor, object, audience) | ||
50 | |||
51 | const serverActor = await getServerActor() | ||
52 | const toFollowersOf = [ byActor, serverActor ] | ||
53 | |||
54 | if (playlist.VideoChannel) toFollowersOf.push(playlist.VideoChannel.Actor) | ||
55 | |||
56 | return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) | ||
57 | } | ||
58 | |||
56 | async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) { | 59 | async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) { |
57 | logger.info('Creating job to send comment %s.', comment.url) | 60 | logger.info('Creating job to send comment %s.', comment.url) |
58 | 61 | ||
@@ -91,37 +94,6 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio | |||
91 | return unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl) | 94 | return unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl) |
92 | } | 95 | } |
93 | 96 | ||
94 | async function sendCreateView (byActor: ActorModel, video: VideoModel, t: Transaction) { | ||
95 | logger.info('Creating job to send view of %s.', video.url) | ||
96 | |||
97 | const url = getVideoViewActivityPubUrl(byActor, video) | ||
98 | const viewActivity = buildViewActivity(url, byActor, video) | ||
99 | |||
100 | return sendVideoRelatedCreateActivity({ | ||
101 | // Use the server actor to send the view | ||
102 | byActor, | ||
103 | video, | ||
104 | url, | ||
105 | object: viewActivity, | ||
106 | transaction: t | ||
107 | }) | ||
108 | } | ||
109 | |||
110 | async function sendCreateDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { | ||
111 | logger.info('Creating job to dislike %s.', video.url) | ||
112 | |||
113 | const url = getVideoDislikeActivityPubUrl(byActor, video) | ||
114 | const dislikeActivity = buildDislikeActivity(url, byActor, video) | ||
115 | |||
116 | return sendVideoRelatedCreateActivity({ | ||
117 | byActor, | ||
118 | video, | ||
119 | url, | ||
120 | object: dislikeActivity, | ||
121 | transaction: t | ||
122 | }) | ||
123 | } | ||
124 | |||
125 | function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { | 97 | function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { |
126 | if (!audience) audience = getAudience(byActor) | 98 | if (!audience) audience = getAudience(byActor) |
127 | 99 | ||
@@ -136,34 +108,13 @@ function buildCreateActivity (url: string, byActor: ActorModel, object: any, aud | |||
136 | ) | 108 | ) |
137 | } | 109 | } |
138 | 110 | ||
139 | function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel) { | ||
140 | return { | ||
141 | id: url, | ||
142 | type: 'Dislike', | ||
143 | actor: byActor.url, | ||
144 | object: video.url | ||
145 | } | ||
146 | } | ||
147 | |||
148 | function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel) { | ||
149 | return { | ||
150 | id: url, | ||
151 | type: 'View', | ||
152 | actor: byActor.url, | ||
153 | object: video.url | ||
154 | } | ||
155 | } | ||
156 | |||
157 | // --------------------------------------------------------------------------- | 111 | // --------------------------------------------------------------------------- |
158 | 112 | ||
159 | export { | 113 | export { |
160 | sendCreateVideo, | 114 | sendCreateVideo, |
161 | sendVideoAbuse, | ||
162 | buildCreateActivity, | 115 | buildCreateActivity, |
163 | sendCreateView, | ||
164 | sendCreateDislike, | ||
165 | buildDislikeActivity, | ||
166 | sendCreateVideoComment, | 116 | sendCreateVideoComment, |
117 | sendCreateVideoPlaylist, | ||
167 | sendCreateCacheFile | 118 | sendCreateCacheFile |
168 | } | 119 | } |
169 | 120 | ||
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts index 18969433a..7bf5ca520 100644 --- a/server/lib/activitypub/send/send-delete.ts +++ b/server/lib/activitypub/send/send-delete.ts | |||
@@ -8,6 +8,8 @@ import { getDeleteActivityPubUrl } from '../url' | |||
8 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 8 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
9 | import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' | 9 | import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' |
10 | import { logger } from '../../../helpers/logger' | 10 | import { logger } from '../../../helpers/logger' |
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
12 | import { getServerActor } from '../../../helpers/utils' | ||
11 | 13 | ||
12 | async function sendDeleteVideo (video: VideoModel, transaction: Transaction) { | 14 | async function sendDeleteVideo (video: VideoModel, transaction: Transaction) { |
13 | logger.info('Creating job to broadcast delete of video %s.', video.url) | 15 | logger.info('Creating job to broadcast delete of video %s.', video.url) |
@@ -29,7 +31,12 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) { | |||
29 | const url = getDeleteActivityPubUrl(byActor.url) | 31 | const url = getDeleteActivityPubUrl(byActor.url) |
30 | const activity = buildDeleteActivity(url, byActor.url, byActor) | 32 | const activity = buildDeleteActivity(url, byActor.url, byActor) |
31 | 33 | ||
32 | const actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t) | 34 | const actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) |
35 | |||
36 | // In case the actor did not have any videos | ||
37 | const serverActor = await getServerActor() | ||
38 | actorsInvolved.push(serverActor) | ||
39 | |||
33 | actorsInvolved.push(byActor) | 40 | actorsInvolved.push(byActor) |
34 | 41 | ||
35 | return broadcastToFollowers(activity, byActor, actorsInvolved, t) | 42 | return broadcastToFollowers(activity, byActor, actorsInvolved, t) |
@@ -64,12 +71,29 @@ async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Trans | |||
64 | return unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl) | 71 | return unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl) |
65 | } | 72 | } |
66 | 73 | ||
74 | async function sendDeleteVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { | ||
75 | logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) | ||
76 | |||
77 | const byActor = videoPlaylist.OwnerAccount.Actor | ||
78 | |||
79 | const url = getDeleteActivityPubUrl(videoPlaylist.url) | ||
80 | const activity = buildDeleteActivity(url, videoPlaylist.url, byActor) | ||
81 | |||
82 | const serverActor = await getServerActor() | ||
83 | const toFollowersOf = [ byActor, serverActor ] | ||
84 | |||
85 | if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) | ||
86 | |||
87 | return broadcastToFollowers(activity, byActor, toFollowersOf, t) | ||
88 | } | ||
89 | |||
67 | // --------------------------------------------------------------------------- | 90 | // --------------------------------------------------------------------------- |
68 | 91 | ||
69 | export { | 92 | export { |
70 | sendDeleteVideo, | 93 | sendDeleteVideo, |
71 | sendDeleteActor, | 94 | sendDeleteActor, |
72 | sendDeleteVideoComment | 95 | sendDeleteVideoComment, |
96 | sendDeleteVideoPlaylist | ||
73 | } | 97 | } |
74 | 98 | ||
75 | // --------------------------------------------------------------------------- | 99 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts new file mode 100644 index 000000000..a88436f2c --- /dev/null +++ b/server/lib/activitypub/send/send-dislike.ts | |||
@@ -0,0 +1,41 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { getVideoDislikeActivityPubUrl } from '../url' | ||
5 | import { logger } from '../../../helpers/logger' | ||
6 | import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub' | ||
7 | import { sendVideoRelatedActivity } from './utils' | ||
8 | import { audiencify, getAudience } from '../audience' | ||
9 | |||
10 | async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { | ||
11 | logger.info('Creating job to dislike %s.', video.url) | ||
12 | |||
13 | const activityBuilder = (audience: ActivityAudience) => { | ||
14 | const url = getVideoDislikeActivityPubUrl(byActor, video) | ||
15 | |||
16 | return buildDislikeActivity(url, byActor, video, audience) | ||
17 | } | ||
18 | |||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | ||
20 | } | ||
21 | |||
22 | function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityDislike { | ||
23 | if (!audience) audience = getAudience(byActor) | ||
24 | |||
25 | return audiencify( | ||
26 | { | ||
27 | id: url, | ||
28 | type: 'Dislike' as 'Dislike', | ||
29 | actor: byActor.url, | ||
30 | object: video.url | ||
31 | }, | ||
32 | audience | ||
33 | ) | ||
34 | } | ||
35 | |||
36 | // --------------------------------------------------------------------------- | ||
37 | |||
38 | export { | ||
39 | sendDislike, | ||
40 | buildDislikeActivity | ||
41 | } | ||
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts new file mode 100644 index 000000000..96a7311b9 --- /dev/null +++ b/server/lib/activitypub/send/send-flag.ts | |||
@@ -0,0 +1,39 @@ | |||
1 | import { ActorModel } from '../../../models/activitypub/actor' | ||
2 | import { VideoModel } from '../../../models/video/video' | ||
3 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
4 | import { getVideoAbuseActivityPubUrl } from '../url' | ||
5 | import { unicastTo } from './utils' | ||
6 | import { logger } from '../../../helpers/logger' | ||
7 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' | ||
8 | import { audiencify, getAudience } from '../audience' | ||
9 | |||
10 | async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel) { | ||
11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user | ||
12 | |||
13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | ||
14 | |||
15 | logger.info('Creating job to send video abuse %s.', url) | ||
16 | |||
17 | // Custom audience, we only send the abuse to the origin instance | ||
18 | const audience = { to: [ video.VideoChannel.Account.Actor.url ], cc: [] } | ||
19 | const flagActivity = buildFlagActivity(url, byActor, videoAbuse, audience) | ||
20 | |||
21 | return unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl) | ||
22 | } | ||
23 | |||
24 | function buildFlagActivity (url: string, byActor: ActorModel, videoAbuse: VideoAbuseModel, audience: ActivityAudience): ActivityFlag { | ||
25 | if (!audience) audience = getAudience(byActor) | ||
26 | |||
27 | const activity = Object.assign( | ||
28 | { id: url, actor: byActor.url }, | ||
29 | videoAbuse.toActivityPubObject() | ||
30 | ) | ||
31 | |||
32 | return audiencify(activity, audience) | ||
33 | } | ||
34 | |||
35 | // --------------------------------------------------------------------------- | ||
36 | |||
37 | export { | ||
38 | sendVideoAbuse | ||
39 | } | ||
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts index 170b46b48..2c3d02014 100644 --- a/server/lib/activitypub/send/send-follow.ts +++ b/server/lib/activitypub/send/send-follow.ts | |||
@@ -14,7 +14,7 @@ function sendFollow (actorFollow: ActorFollowModel) { | |||
14 | 14 | ||
15 | logger.info('Creating job to send follow request to %s.', following.url) | 15 | logger.info('Creating job to send follow request to %s.', following.url) |
16 | 16 | ||
17 | const url = getActorFollowActivityPubUrl(actorFollow) | 17 | const url = getActorFollowActivityPubUrl(me, following) |
18 | const data = buildFollowActivity(url, me, following) | 18 | const data = buildFollowActivity(url, me, following) |
19 | 19 | ||
20 | return unicastTo(data, me, following.inboxUrl) | 20 | return unicastTo(data, me, following.inboxUrl) |
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts new file mode 100644 index 000000000..bac7ff556 --- /dev/null +++ b/server/lib/activitypub/send/send-reject.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' | ||
2 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' | ||
4 | import { unicastTo } from './utils' | ||
5 | import { buildFollowActivity } from './send-follow' | ||
6 | import { logger } from '../../../helpers/logger' | ||
7 | |||
8 | async function sendReject (follower: ActorModel, following: ActorModel) { | ||
9 | if (!follower.serverId) { // This should never happen | ||
10 | logger.warn('Do not sending reject to local follower.') | ||
11 | return | ||
12 | } | ||
13 | |||
14 | logger.info('Creating job to reject follower %s.', follower.url) | ||
15 | |||
16 | const followUrl = getActorFollowActivityPubUrl(follower, following) | ||
17 | const followData = buildFollowActivity(followUrl, follower, following) | ||
18 | |||
19 | const url = getActorFollowRejectActivityPubUrl(follower, following) | ||
20 | const data = buildRejectActivity(url, following, followData) | ||
21 | |||
22 | return unicastTo(data, following, follower.inboxUrl) | ||
23 | } | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
26 | |||
27 | export { | ||
28 | sendReject | ||
29 | } | ||
30 | |||
31 | // --------------------------------------------------------------------------- | ||
32 | |||
33 | function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject { | ||
34 | return { | ||
35 | type: 'Reject', | ||
36 | id: url, | ||
37 | actor: byActor.url, | ||
38 | object: followActivityData | ||
39 | } | ||
40 | } | ||
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts index bf1b6e117..8727a121e 100644 --- a/server/lib/activitypub/send/send-undo.ts +++ b/server/lib/activitypub/send/send-undo.ts | |||
@@ -2,7 +2,7 @@ import { Transaction } from 'sequelize' | |||
2 | import { | 2 | import { |
3 | ActivityAnnounce, | 3 | ActivityAnnounce, |
4 | ActivityAudience, | 4 | ActivityAudience, |
5 | ActivityCreate, | 5 | ActivityCreate, ActivityDislike, |
6 | ActivityFollow, | 6 | ActivityFollow, |
7 | ActivityLike, | 7 | ActivityLike, |
8 | ActivityUndo | 8 | ActivityUndo |
@@ -13,13 +13,14 @@ import { VideoModel } from '../../../models/video/video' | |||
13 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' | 13 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' |
14 | import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 14 | import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
15 | import { audiencify, getAudience } from '../audience' | 15 | import { audiencify, getAudience } from '../audience' |
16 | import { buildCreateActivity, buildDislikeActivity } from './send-create' | 16 | import { buildCreateActivity } from './send-create' |
17 | import { buildFollowActivity } from './send-follow' | 17 | import { buildFollowActivity } from './send-follow' |
18 | import { buildLikeActivity } from './send-like' | 18 | import { buildLikeActivity } from './send-like' |
19 | import { VideoShareModel } from '../../../models/video/video-share' | 19 | import { VideoShareModel } from '../../../models/video/video-share' |
20 | import { buildAnnounceWithVideoAudience } from './send-announce' | 20 | import { buildAnnounceWithVideoAudience } from './send-announce' |
21 | import { logger } from '../../../helpers/logger' | 21 | import { logger } from '../../../helpers/logger' |
22 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | 22 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' |
23 | import { buildDislikeActivity } from './send-dislike' | ||
23 | 24 | ||
24 | async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { | 25 | async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { |
25 | const me = actorFollow.ActorFollower | 26 | const me = actorFollow.ActorFollower |
@@ -30,7 +31,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { | |||
30 | 31 | ||
31 | logger.info('Creating job to send an unfollow request to %s.', following.url) | 32 | logger.info('Creating job to send an unfollow request to %s.', following.url) |
32 | 33 | ||
33 | const followUrl = getActorFollowActivityPubUrl(actorFollow) | 34 | const followUrl = getActorFollowActivityPubUrl(me, following) |
34 | const undoUrl = getUndoActivityPubUrl(followUrl) | 35 | const undoUrl = getUndoActivityPubUrl(followUrl) |
35 | 36 | ||
36 | const followActivity = buildFollowActivity(followUrl, me, following) | 37 | const followActivity = buildFollowActivity(followUrl, me, following) |
@@ -65,15 +66,15 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans | |||
65 | 66 | ||
66 | const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) | 67 | const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) |
67 | const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video) | 68 | const dislikeActivity = buildDislikeActivity(dislikeUrl, byActor, video) |
68 | const createDislikeActivity = buildCreateActivity(dislikeUrl, byActor, dislikeActivity) | ||
69 | 69 | ||
70 | return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: createDislikeActivity, transaction: t }) | 70 | return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) |
71 | } | 71 | } |
72 | 72 | ||
73 | async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) { | 73 | async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) { |
74 | logger.info('Creating job to undo cache file %s.', redundancyModel.url) | 74 | logger.info('Creating job to undo cache file %s.', redundancyModel.url) |
75 | 75 | ||
76 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.VideoFile.Video.id) | 76 | const videoId = redundancyModel.getVideo().id |
77 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | ||
77 | const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject()) | 78 | const createActivity = buildCreateActivity(redundancyModel.url, byActor, redundancyModel.toActivityPubObject()) |
78 | 79 | ||
79 | return sendUndoVideoRelatedActivity({ byActor, video, url: redundancyModel.url, activity: createActivity, transaction: t }) | 80 | return sendUndoVideoRelatedActivity({ byActor, video, url: redundancyModel.url, activity: createActivity, transaction: t }) |
@@ -94,7 +95,7 @@ export { | |||
94 | function undoActivityData ( | 95 | function undoActivityData ( |
95 | url: string, | 96 | url: string, |
96 | byActor: ActorModel, | 97 | byActor: ActorModel, |
97 | object: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce, | 98 | object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, |
98 | audience?: ActivityAudience | 99 | audience?: ActivityAudience |
99 | ): ActivityUndo { | 100 | ): ActivityUndo { |
100 | if (!audience) audience = getAudience(byActor) | 101 | if (!audience) audience = getAudience(byActor) |
@@ -114,7 +115,7 @@ async function sendUndoVideoRelatedActivity (options: { | |||
114 | byActor: ActorModel, | 115 | byActor: ActorModel, |
115 | video: VideoModel, | 116 | video: VideoModel, |
116 | url: string, | 117 | url: string, |
117 | activity: ActivityFollow | ActivityLike | ActivityCreate | ActivityAnnounce, | 118 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, |
118 | transaction: Transaction | 119 | transaction: Transaction |
119 | }) { | 120 | }) { |
120 | const activityBuilder = (audience: ActivityAudience) => { | 121 | const activityBuilder = (audience: ActivityAudience) => { |
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index a68f03edf..7411c08d5 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts | |||
@@ -12,8 +12,13 @@ import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' | |||
12 | import { logger } from '../../../helpers/logger' | 12 | import { logger } from '../../../helpers/logger' |
13 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 13 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
14 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | 14 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' |
15 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
16 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
17 | import { getServerActor } from '../../../helpers/utils' | ||
15 | 18 | ||
16 | async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) { | 19 | async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) { |
20 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | ||
21 | |||
17 | logger.info('Creating job to update video %s.', video.url) | 22 | logger.info('Creating job to update video %s.', video.url) |
18 | 23 | ||
19 | const byActor = overrodeByActor ? overrodeByActor : video.VideoChannel.Account.Actor | 24 | const byActor = overrodeByActor ? overrodeByActor : video.VideoChannel.Account.Actor |
@@ -47,7 +52,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod | |||
47 | let actorsInvolved: ActorModel[] | 52 | let actorsInvolved: ActorModel[] |
48 | if (accountOrChannel instanceof AccountModel) { | 53 | if (accountOrChannel instanceof AccountModel) { |
49 | // Actors that shared my videos are involved too | 54 | // Actors that shared my videos are involved too |
50 | actorsInvolved = await VideoShareModel.loadActorsByVideoOwner(byActor.id, t) | 55 | actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) |
51 | } else { | 56 | } else { |
52 | // Actors that shared videos of my channel are involved too | 57 | // Actors that shared videos of my channel are involved too |
53 | actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, t) | 58 | actorsInvolved = await VideoShareModel.loadActorsByVideoChannel(accountOrChannel.id, t) |
@@ -61,7 +66,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod | |||
61 | async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) { | 66 | async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) { |
62 | logger.info('Creating job to update cache file %s.', redundancyModel.url) | 67 | logger.info('Creating job to update cache file %s.', redundancyModel.url) |
63 | 68 | ||
64 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.VideoFile.Video.id) | 69 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id) |
65 | 70 | ||
66 | const activityBuilder = (audience: ActivityAudience) => { | 71 | const activityBuilder = (audience: ActivityAudience) => { |
67 | const redundancyObject = redundancyModel.toActivityPubObject() | 72 | const redundancyObject = redundancyModel.toActivityPubObject() |
@@ -73,12 +78,35 @@ async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoR | |||
73 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) | 78 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) |
74 | } | 79 | } |
75 | 80 | ||
81 | async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { | ||
82 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined | ||
83 | |||
84 | const byActor = videoPlaylist.OwnerAccount.Actor | ||
85 | |||
86 | logger.info('Creating job to update video playlist %s.', videoPlaylist.url) | ||
87 | |||
88 | const url = getUpdateActivityPubUrl(videoPlaylist.url, videoPlaylist.updatedAt.toISOString()) | ||
89 | |||
90 | const object = await videoPlaylist.toActivityPubObject(null, t) | ||
91 | const audience = getAudience(byActor, videoPlaylist.privacy === VideoPlaylistPrivacy.PUBLIC) | ||
92 | |||
93 | const updateActivity = buildUpdateActivity(url, byActor, object, audience) | ||
94 | |||
95 | const serverActor = await getServerActor() | ||
96 | const toFollowersOf = [ byActor, serverActor ] | ||
97 | |||
98 | if (videoPlaylist.VideoChannel) toFollowersOf.push(videoPlaylist.VideoChannel.Actor) | ||
99 | |||
100 | return broadcastToFollowers(updateActivity, byActor, toFollowersOf, t) | ||
101 | } | ||
102 | |||
76 | // --------------------------------------------------------------------------- | 103 | // --------------------------------------------------------------------------- |
77 | 104 | ||
78 | export { | 105 | export { |
79 | sendUpdateActor, | 106 | sendUpdateActor, |
80 | sendUpdateVideo, | 107 | sendUpdateVideo, |
81 | sendUpdateCacheFile | 108 | sendUpdateCacheFile, |
109 | sendUpdateVideoPlaylist | ||
82 | } | 110 | } |
83 | 111 | ||
84 | // --------------------------------------------------------------------------- | 112 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts new file mode 100644 index 000000000..8ad126be0 --- /dev/null +++ b/server/lib/activitypub/send/send-view.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import { Transaction } from 'sequelize' | ||
2 | import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' | ||
3 | import { ActorModel } from '../../../models/activitypub/actor' | ||
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { getVideoLikeActivityPubUrl } from '../url' | ||
6 | import { sendVideoRelatedActivity } from './utils' | ||
7 | import { audiencify, getAudience } from '../audience' | ||
8 | import { logger } from '../../../helpers/logger' | ||
9 | |||
10 | async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) { | ||
11 | logger.info('Creating job to send view of %s.', video.url) | ||
12 | |||
13 | const activityBuilder = (audience: ActivityAudience) => { | ||
14 | const url = getVideoLikeActivityPubUrl(byActor, video) | ||
15 | |||
16 | return buildViewActivity(url, byActor, video, audience) | ||
17 | } | ||
18 | |||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | ||
20 | } | ||
21 | |||
22 | function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityView { | ||
23 | if (!audience) audience = getAudience(byActor) | ||
24 | |||
25 | return audiencify( | ||
26 | { | ||
27 | id: url, | ||
28 | type: 'View' as 'View', | ||
29 | actor: byActor.url, | ||
30 | object: video.url | ||
31 | }, | ||
32 | audience | ||
33 | ) | ||
34 | } | ||
35 | |||
36 | // --------------------------------------------------------------------------- | ||
37 | |||
38 | export { | ||
39 | sendView | ||
40 | } | ||
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index 1767df0ae..7f38402b6 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts | |||
@@ -10,7 +10,7 @@ import * as Bluebird from 'bluebird' | |||
10 | import { doRequest } from '../../helpers/requests' | 10 | import { doRequest } from '../../helpers/requests' |
11 | import { getOrCreateActorAndServerAndModel } from './actor' | 11 | import { getOrCreateActorAndServerAndModel } from './actor' |
12 | import { logger } from '../../helpers/logger' | 12 | import { logger } from '../../helpers/logger' |
13 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers' | 13 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
14 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 14 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
15 | 15 | ||
16 | async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { | 16 | async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { |
@@ -54,12 +54,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) { | |||
54 | url: shareUrl | 54 | url: shareUrl |
55 | } | 55 | } |
56 | 56 | ||
57 | await VideoShareModel.findOrCreate({ | 57 | await VideoShareModel.upsert(entry) |
58 | where: { | ||
59 | url: shareUrl | ||
60 | }, | ||
61 | defaults: entry | ||
62 | }) | ||
63 | } catch (err) { | 58 | } catch (err) { |
64 | logger.warn('Cannot add share %s.', shareUrl, { err }) | 59 | logger.warn('Cannot add share %s.', shareUrl, { err }) |
65 | } | 60 | } |
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index 38f15448c..bcb7a4ee2 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts | |||
@@ -1,35 +1,49 @@ | |||
1 | import { CONFIG } from '../../initializers' | 1 | import { WEBSERVER } from '../../initializers/constants' |
2 | import { ActorModel } from '../../models/activitypub/actor' | 2 | import { ActorModel } from '../../models/activitypub/actor' |
3 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
4 | import { VideoModel } from '../../models/video/video' | 4 | import { VideoModel } from '../../models/video/video' |
5 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 5 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
6 | import { VideoCommentModel } from '../../models/video/video-comment' | 6 | import { VideoCommentModel } from '../../models/video/video-comment' |
7 | import { VideoFileModel } from '../../models/video/video-file' | 7 | import { VideoFileModel } from '../../models/video/video-file' |
8 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | ||
9 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | ||
8 | 10 | ||
9 | function getVideoActivityPubUrl (video: VideoModel) { | 11 | function getVideoActivityPubUrl (video: VideoModel) { |
10 | return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid | 12 | return WEBSERVER.URL + '/videos/watch/' + video.uuid |
13 | } | ||
14 | |||
15 | function getVideoPlaylistActivityPubUrl (videoPlaylist: VideoPlaylistModel) { | ||
16 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid | ||
17 | } | ||
18 | |||
19 | function getVideoPlaylistElementActivityPubUrl (videoPlaylist: VideoPlaylistModel, video: VideoModel) { | ||
20 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid | ||
11 | } | 21 | } |
12 | 22 | ||
13 | function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) { | 23 | function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) { |
14 | const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' | 24 | const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' |
15 | 25 | ||
16 | return `${CONFIG.WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` | 26 | return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` |
27 | } | ||
28 | |||
29 | function getVideoCacheStreamingPlaylistActivityPubUrl (video: VideoModel, playlist: VideoStreamingPlaylistModel) { | ||
30 | return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}` | ||
17 | } | 31 | } |
18 | 32 | ||
19 | function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { | 33 | function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { |
20 | return CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id | 34 | return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id |
21 | } | 35 | } |
22 | 36 | ||
23 | function getVideoChannelActivityPubUrl (videoChannelName: string) { | 37 | function getVideoChannelActivityPubUrl (videoChannelName: string) { |
24 | return CONFIG.WEBSERVER.URL + '/video-channels/' + videoChannelName | 38 | return WEBSERVER.URL + '/video-channels/' + videoChannelName |
25 | } | 39 | } |
26 | 40 | ||
27 | function getAccountActivityPubUrl (accountName: string) { | 41 | function getAccountActivityPubUrl (accountName: string) { |
28 | return CONFIG.WEBSERVER.URL + '/accounts/' + accountName | 42 | return WEBSERVER.URL + '/accounts/' + accountName |
29 | } | 43 | } |
30 | 44 | ||
31 | function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) { | 45 | function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) { |
32 | return CONFIG.WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id | 46 | return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id |
33 | } | 47 | } |
34 | 48 | ||
35 | function getVideoViewActivityPubUrl (byActor: ActorModel, video: VideoModel) { | 49 | function getVideoViewActivityPubUrl (byActor: ActorModel, video: VideoModel) { |
@@ -60,11 +74,8 @@ function getVideoDislikesActivityPubUrl (video: VideoModel) { | |||
60 | return video.url + '/dislikes' | 74 | return video.url + '/dislikes' |
61 | } | 75 | } |
62 | 76 | ||
63 | function getActorFollowActivityPubUrl (actorFollow: ActorFollowModel) { | 77 | function getActorFollowActivityPubUrl (follower: ActorModel, following: ActorModel) { |
64 | const me = actorFollow.ActorFollower | 78 | return follower.url + '/follows/' + following.id |
65 | const following = actorFollow.ActorFollowing | ||
66 | |||
67 | return me.url + '/follows/' + following.id | ||
68 | } | 79 | } |
69 | 80 | ||
70 | function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModel) { | 81 | function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModel) { |
@@ -74,6 +85,10 @@ function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModel) { | |||
74 | return follower.url + '/accepts/follows/' + me.id | 85 | return follower.url + '/accepts/follows/' + me.id |
75 | } | 86 | } |
76 | 87 | ||
88 | function getActorFollowRejectActivityPubUrl (follower: ActorModel, following: ActorModel) { | ||
89 | return follower.url + '/rejects/follows/' + following.id | ||
90 | } | ||
91 | |||
77 | function getVideoAnnounceActivityPubUrl (byActor: ActorModel, video: VideoModel) { | 92 | function getVideoAnnounceActivityPubUrl (byActor: ActorModel, video: VideoModel) { |
78 | return video.url + '/announces/' + byActor.id | 93 | return video.url + '/announces/' + byActor.id |
79 | } | 94 | } |
@@ -92,6 +107,9 @@ function getUndoActivityPubUrl (originalUrl: string) { | |||
92 | 107 | ||
93 | export { | 108 | export { |
94 | getVideoActivityPubUrl, | 109 | getVideoActivityPubUrl, |
110 | getVideoPlaylistElementActivityPubUrl, | ||
111 | getVideoPlaylistActivityPubUrl, | ||
112 | getVideoCacheStreamingPlaylistActivityPubUrl, | ||
95 | getVideoChannelActivityPubUrl, | 113 | getVideoChannelActivityPubUrl, |
96 | getAccountActivityPubUrl, | 114 | getAccountActivityPubUrl, |
97 | getVideoAbuseActivityPubUrl, | 115 | getVideoAbuseActivityPubUrl, |
@@ -103,6 +121,7 @@ export { | |||
103 | getVideoViewActivityPubUrl, | 121 | getVideoViewActivityPubUrl, |
104 | getVideoLikeActivityPubUrl, | 122 | getVideoLikeActivityPubUrl, |
105 | getVideoDislikeActivityPubUrl, | 123 | getVideoDislikeActivityPubUrl, |
124 | getActorFollowRejectActivityPubUrl, | ||
106 | getVideoCommentActivityPubUrl, | 125 | getVideoCommentActivityPubUrl, |
107 | getDeleteActivityPubUrl, | 126 | getDeleteActivityPubUrl, |
108 | getVideoSharesActivityPubUrl, | 127 | getVideoSharesActivityPubUrl, |
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index e87301fe7..18f44d50e 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -2,7 +2,7 @@ import { VideoCommentObject } from '../../../shared/models/activitypub/objects/v | |||
2 | import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' | 2 | import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validators/activitypub/video-comments' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { doRequest } from '../../helpers/requests' | 4 | import { doRequest } from '../../helpers/requests' |
5 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers' | 5 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
6 | import { ActorModel } from '../../models/activitypub/actor' | 6 | import { ActorModel } from '../../models/activitypub/actor' |
7 | import { VideoModel } from '../../models/video/video' | 7 | import { VideoModel } from '../../models/video/video' |
8 | import { VideoCommentModel } from '../../models/video/video-comment' | 8 | import { VideoCommentModel } from '../../models/video/video-comment' |
@@ -34,8 +34,7 @@ async function videoCommentActivityObjectToDBAttributes (video: VideoModel, acto | |||
34 | accountId: actor.Account.id, | 34 | accountId: actor.Account.id, |
35 | inReplyToCommentId, | 35 | inReplyToCommentId, |
36 | originCommentId, | 36 | originCommentId, |
37 | createdAt: new Date(comment.published), | 37 | createdAt: new Date(comment.published) |
38 | updatedAt: new Date(comment.updated) | ||
39 | } | 38 | } |
40 | } | 39 | } |
41 | 40 | ||
@@ -74,12 +73,7 @@ async function addVideoComment (videoInstance: VideoModel, commentUrl: string) { | |||
74 | const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) | 73 | const entry = await videoCommentActivityObjectToDBAttributes(videoInstance, actor, body) |
75 | if (!entry) return { created: false } | 74 | if (!entry) return { created: false } |
76 | 75 | ||
77 | const [ comment, created ] = await VideoCommentModel.findOrCreate({ | 76 | const [ comment, created ] = await VideoCommentModel.upsert<VideoCommentModel>(entry, { returning: true }) |
78 | where: { | ||
79 | url: body.id | ||
80 | }, | ||
81 | defaults: entry | ||
82 | }) | ||
83 | comment.Account = actor.Account | 77 | comment.Account = actor.Account |
84 | comment.Video = videoInstance | 78 | comment.Video = videoInstance |
85 | 79 | ||
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts index 45a2b22ea..cda5b2981 100644 --- a/server/lib/activitypub/video-rates.ts +++ b/server/lib/activitypub/video-rates.ts | |||
@@ -1,17 +1,18 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { AccountModel } from '../../models/account/account' | 2 | import { AccountModel } from '../../models/account/account' |
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { sendCreateDislike, sendLike, sendUndoDislike, sendUndoLike } from './send' | 4 | import { sendLike, sendUndoDislike, sendUndoLike } from './send' |
5 | import { VideoRateType } from '../../../shared/models/videos' | 5 | import { VideoRateType } from '../../../shared/models/videos' |
6 | import * as Bluebird from 'bluebird' | 6 | import * as Bluebird from 'bluebird' |
7 | import { getOrCreateActorAndServerAndModel } from './actor' | 7 | import { getOrCreateActorAndServerAndModel } from './actor' |
8 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | 8 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' |
9 | import { logger } from '../../helpers/logger' | 9 | import { logger } from '../../helpers/logger' |
10 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers' | 10 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
11 | import { doRequest } from '../../helpers/requests' | 11 | import { doRequest } from '../../helpers/requests' |
12 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 12 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
13 | import { ActorModel } from '../../models/activitypub/actor' | 13 | import { ActorModel } from '../../models/activitypub/actor' |
14 | import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' | 14 | import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' |
15 | import { sendDislike } from './send/send-dislike' | ||
15 | 16 | ||
16 | async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) { | 17 | async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) { |
17 | let rateCounts = 0 | 18 | let rateCounts = 0 |
@@ -37,19 +38,14 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa | |||
37 | 38 | ||
38 | const actor = await getOrCreateActorAndServerAndModel(actorUrl) | 39 | const actor = await getOrCreateActorAndServerAndModel(actorUrl) |
39 | 40 | ||
40 | const [ , created ] = await AccountVideoRateModel | 41 | const entry = { |
41 | .findOrCreate({ | 42 | videoId: video.id, |
42 | where: { | 43 | accountId: actor.Account.id, |
43 | videoId: video.id, | 44 | type: rate, |
44 | accountId: actor.Account.id | 45 | url: body.id |
45 | }, | 46 | } |
46 | defaults: { | 47 | |
47 | videoId: video.id, | 48 | const created = await AccountVideoRateModel.upsert(entry) |
48 | accountId: actor.Account.id, | ||
49 | type: rate, | ||
50 | url: body.id | ||
51 | } | ||
52 | }) | ||
53 | 49 | ||
54 | if (created) rateCounts += 1 | 50 | if (created) rateCounts += 1 |
55 | } catch (err) { | 51 | } catch (err) { |
@@ -60,7 +56,10 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa | |||
60 | logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid) | 56 | logger.info('Adding %d %s to video %s.', rateCounts, rate, video.uuid) |
61 | 57 | ||
62 | // This is "likes" and "dislikes" | 58 | // This is "likes" and "dislikes" |
63 | if (rateCounts !== 0) await video.increment(rate + 's', { by: rateCounts }) | 59 | if (rateCounts !== 0) { |
60 | const field = rate === 'like' ? 'likes' : 'dislikes' | ||
61 | await video.increment(field, { by: rateCounts }) | ||
62 | } | ||
64 | 63 | ||
65 | return | 64 | return |
66 | } | 65 | } |
@@ -82,7 +81,7 @@ async function sendVideoRateChange (account: AccountModel, | |||
82 | // Like | 81 | // Like |
83 | if (likes > 0) await sendLike(actor, video, t) | 82 | if (likes > 0) await sendLike(actor, video, t) |
84 | // Dislike | 83 | // Dislike |
85 | if (dislikes > 0) await sendCreateDislike(actor, video, t) | 84 | if (dislikes > 0) await sendDislike(actor, video, t) |
86 | } | 85 | } |
87 | 86 | ||
88 | function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) { | 87 | function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) { |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index e1e523499..4f26cb6be 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -2,15 +2,28 @@ import * as Bluebird from 'bluebird' | |||
2 | import * as sequelize from 'sequelize' | 2 | import * as sequelize from 'sequelize' |
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import * as request from 'request' | 4 | import * as request from 'request' |
5 | import { ActivityIconObject, ActivityUrlObject, ActivityVideoUrlObject, VideoState } from '../../../shared/index' | 5 | import { |
6 | ActivityPlaylistSegmentHashesObject, | ||
7 | ActivityPlaylistUrlObject, | ||
8 | ActivityUrlObject, | ||
9 | ActivityVideoUrlObject, | ||
10 | VideoState | ||
11 | } from '../../../shared/index' | ||
6 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 12 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
7 | import { VideoPrivacy } from '../../../shared/models/videos' | 13 | import { VideoPrivacy } from '../../../shared/models/videos' |
8 | import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' | 14 | import { sanitizeAndCheckVideoTorrentObject } from '../../helpers/custom-validators/activitypub/videos' |
9 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 15 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' |
10 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' | 16 | import { resetSequelizeInstance, retryTransactionWrapper } from '../../helpers/database-utils' |
11 | import { logger } from '../../helpers/logger' | 17 | import { logger } from '../../helpers/logger' |
12 | import { doRequest, downloadImage } from '../../helpers/requests' | 18 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' |
13 | import { ACTIVITY_PUB, CONFIG, MIMETYPES, REMOTE_SCHEME, sequelizeTypescript, THUMBNAILS_SIZE } from '../../initializers' | 19 | import { |
20 | ACTIVITY_PUB, | ||
21 | MIMETYPES, | ||
22 | P2P_MEDIA_LOADER_PEER_VERSION, | ||
23 | PREVIEWS_SIZE, | ||
24 | REMOTE_SCHEME, | ||
25 | STATIC_PATHS | ||
26 | } from '../../initializers/constants' | ||
14 | import { ActorModel } from '../../models/activitypub/actor' | 27 | import { ActorModel } from '../../models/activitypub/actor' |
15 | import { TagModel } from '../../models/video/tag' | 28 | import { TagModel } from '../../models/video/tag' |
16 | import { VideoModel } from '../../models/video/video' | 29 | import { VideoModel } from '../../models/video/video' |
@@ -30,9 +43,20 @@ import { AccountModel } from '../../models/account/account' | |||
30 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' | 43 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' |
31 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 44 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
32 | import { Notifier } from '../notifier' | 45 | import { Notifier } from '../notifier' |
46 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | ||
47 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | ||
48 | import { AccountVideoRateModel } from '../../models/account/account-video-rate' | ||
49 | import { VideoShareModel } from '../../models/video/video-share' | ||
50 | import { VideoCommentModel } from '../../models/video/video-comment' | ||
51 | import { sequelizeTypescript } from '../../initializers/database' | ||
52 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' | ||
53 | import { ThumbnailModel } from '../../models/video/thumbnail' | ||
54 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
55 | import { join } from 'path' | ||
56 | import { FilteredModelAttributes } from '../../typings/sequelize' | ||
33 | 57 | ||
34 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { | 58 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { |
35 | // If the video is not private and published, we federate it | 59 | // If the video is not private and is published, we federate it |
36 | if (video.privacy !== VideoPrivacy.PRIVATE && video.state === VideoState.PUBLISHED) { | 60 | if (video.privacy !== VideoPrivacy.PRIVATE && video.state === VideoState.PUBLISHED) { |
37 | // Fetch more attributes that we will need to serialize in AP object | 61 | // Fetch more attributes that we will need to serialize in AP object |
38 | if (isArray(video.VideoCaptions) === false) { | 62 | if (isArray(video.VideoCaptions) === false) { |
@@ -84,19 +108,17 @@ async function fetchRemoteVideoDescription (video: VideoModel) { | |||
84 | return body.description ? body.description : '' | 108 | return body.description ? body.description : '' |
85 | } | 109 | } |
86 | 110 | ||
87 | function fetchRemoteVideoStaticFile (video: VideoModel, path: string, reject: Function) { | 111 | function fetchRemoteVideoStaticFile (video: VideoModel, path: string, destPath: string) { |
88 | const host = video.VideoChannel.Account.Actor.Server.host | 112 | const url = buildRemoteBaseUrl(video, path) |
89 | 113 | ||
90 | // We need to provide a callback, if no we could have an uncaught exception | 114 | // We need to provide a callback, if no we could have an uncaught exception |
91 | return request.get(REMOTE_SCHEME.HTTP + '://' + host + path, err => { | 115 | return doRequestAndSaveToFile({ uri: url }, destPath) |
92 | if (err) reject(err) | ||
93 | }) | ||
94 | } | 116 | } |
95 | 117 | ||
96 | function generateThumbnailFromUrl (video: VideoModel, icon: ActivityIconObject) { | 118 | function buildRemoteBaseUrl (video: VideoModel, path: string) { |
97 | const thumbnailName = video.getThumbnailName() | 119 | const host = video.VideoChannel.Account.Actor.Server.host |
98 | 120 | ||
99 | return downloadImage(icon.url, CONFIG.STORAGE.THUMBNAILS_DIR, thumbnailName, THUMBNAILS_SIZE) | 121 | return REMOTE_SCHEME.HTTP + '://' + host + path |
100 | } | 122 | } |
101 | 123 | ||
102 | function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { | 124 | function getOrCreateVideoChannelFromVideoObject (videoObject: VideoTorrentObject) { |
@@ -124,31 +146,43 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid | |||
124 | const jobPayloads: ActivitypubHttpFetcherPayload[] = [] | 146 | const jobPayloads: ActivitypubHttpFetcherPayload[] = [] |
125 | 147 | ||
126 | if (syncParam.likes === true) { | 148 | if (syncParam.likes === true) { |
127 | await crawlCollectionPage<string>(fetchedVideo.likes, items => createRates(items, video, 'like')) | 149 | const handler = items => createRates(items, video, 'like') |
150 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate) | ||
151 | |||
152 | await crawlCollectionPage<string>(fetchedVideo.likes, handler, cleaner) | ||
128 | .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err })) | 153 | .catch(err => logger.error('Cannot add likes of video %s.', video.uuid, { err })) |
129 | } else { | 154 | } else { |
130 | jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) | 155 | jobPayloads.push({ uri: fetchedVideo.likes, videoId: video.id, type: 'video-likes' as 'video-likes' }) |
131 | } | 156 | } |
132 | 157 | ||
133 | if (syncParam.dislikes === true) { | 158 | if (syncParam.dislikes === true) { |
134 | await crawlCollectionPage<string>(fetchedVideo.dislikes, items => createRates(items, video, 'dislike')) | 159 | const handler = items => createRates(items, video, 'dislike') |
160 | const cleaner = crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate) | ||
161 | |||
162 | await crawlCollectionPage<string>(fetchedVideo.dislikes, handler, cleaner) | ||
135 | .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err })) | 163 | .catch(err => logger.error('Cannot add dislikes of video %s.', video.uuid, { err })) |
136 | } else { | 164 | } else { |
137 | jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) | 165 | jobPayloads.push({ uri: fetchedVideo.dislikes, videoId: video.id, type: 'video-dislikes' as 'video-dislikes' }) |
138 | } | 166 | } |
139 | 167 | ||
140 | if (syncParam.shares === true) { | 168 | if (syncParam.shares === true) { |
141 | await crawlCollectionPage<string>(fetchedVideo.shares, items => addVideoShares(items, video)) | 169 | const handler = items => addVideoShares(items, video) |
170 | const cleaner = crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate) | ||
171 | |||
172 | await crawlCollectionPage<string>(fetchedVideo.shares, handler, cleaner) | ||
142 | .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err })) | 173 | .catch(err => logger.error('Cannot add shares of video %s.', video.uuid, { err })) |
143 | } else { | 174 | } else { |
144 | jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) | 175 | jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) |
145 | } | 176 | } |
146 | 177 | ||
147 | if (syncParam.comments === true) { | 178 | if (syncParam.comments === true) { |
148 | await crawlCollectionPage<string>(fetchedVideo.comments, items => addVideoComments(items, video)) | 179 | const handler = items => addVideoComments(items, video) |
180 | const cleaner = crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate) | ||
181 | |||
182 | await crawlCollectionPage<string>(fetchedVideo.comments, handler, cleaner) | ||
149 | .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err })) | 183 | .catch(err => logger.error('Cannot add comments of video %s.', video.uuid, { err })) |
150 | } else { | 184 | } else { |
151 | jobPayloads.push({ uri: fetchedVideo.shares, videoId: video.id, type: 'video-shares' as 'video-shares' }) | 185 | jobPayloads.push({ uri: fetchedVideo.comments, videoId: video.id, type: 'video-comments' as 'video-comments' }) |
152 | } | 186 | } |
153 | 187 | ||
154 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) | 188 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) |
@@ -170,8 +204,7 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
170 | 204 | ||
171 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) | 205 | let videoFromDatabase = await fetchVideoByUrl(videoUrl, fetchType) |
172 | if (videoFromDatabase) { | 206 | if (videoFromDatabase) { |
173 | 207 | if (videoFromDatabase.isOutdated() && allowRefresh === true) { | |
174 | if (allowRefresh === true) { | ||
175 | const refreshOptions = { | 208 | const refreshOptions = { |
176 | video: videoFromDatabase, | 209 | video: videoFromDatabase, |
177 | fetchedType: fetchType, | 210 | fetchedType: fetchType, |
@@ -210,6 +243,14 @@ async function updateVideoFromAP (options: { | |||
210 | const wasUnlistedVideo = options.video.privacy === VideoPrivacy.UNLISTED | 243 | const wasUnlistedVideo = options.video.privacy === VideoPrivacy.UNLISTED |
211 | 244 | ||
212 | try { | 245 | try { |
246 | let thumbnailModel: ThumbnailModel | ||
247 | |||
248 | try { | ||
249 | thumbnailModel = await createVideoMiniatureFromUrl(options.videoObject.icon.url, options.video, ThumbnailType.MINIATURE) | ||
250 | } catch (err) { | ||
251 | logger.warn('Cannot generate thumbnail of %s.', options.videoObject.id, { err }) | ||
252 | } | ||
253 | |||
213 | await sequelizeTypescript.transaction(async t => { | 254 | await sequelizeTypescript.transaction(async t => { |
214 | const sequelizeOptions = { transaction: t } | 255 | const sequelizeOptions = { transaction: t } |
215 | 256 | ||
@@ -233,17 +274,26 @@ async function updateVideoFromAP (options: { | |||
233 | options.video.set('support', videoData.support) | 274 | options.video.set('support', videoData.support) |
234 | options.video.set('nsfw', videoData.nsfw) | 275 | options.video.set('nsfw', videoData.nsfw) |
235 | options.video.set('commentsEnabled', videoData.commentsEnabled) | 276 | options.video.set('commentsEnabled', videoData.commentsEnabled) |
277 | options.video.set('downloadEnabled', videoData.downloadEnabled) | ||
236 | options.video.set('waitTranscoding', videoData.waitTranscoding) | 278 | options.video.set('waitTranscoding', videoData.waitTranscoding) |
237 | options.video.set('state', videoData.state) | 279 | options.video.set('state', videoData.state) |
238 | options.video.set('duration', videoData.duration) | 280 | options.video.set('duration', videoData.duration) |
239 | options.video.set('createdAt', videoData.createdAt) | 281 | options.video.set('createdAt', videoData.createdAt) |
240 | options.video.set('publishedAt', videoData.publishedAt) | 282 | options.video.set('publishedAt', videoData.publishedAt) |
283 | options.video.set('originallyPublishedAt', videoData.originallyPublishedAt) | ||
241 | options.video.set('privacy', videoData.privacy) | 284 | options.video.set('privacy', videoData.privacy) |
242 | options.video.set('channelId', videoData.channelId) | 285 | options.video.set('channelId', videoData.channelId) |
243 | options.video.set('views', videoData.views) | 286 | options.video.set('views', videoData.views) |
244 | 287 | ||
245 | await options.video.save(sequelizeOptions) | 288 | await options.video.save(sequelizeOptions) |
246 | 289 | ||
290 | if (thumbnailModel) if (thumbnailModel) await options.video.addAndSaveThumbnail(thumbnailModel, t) | ||
291 | |||
292 | // FIXME: use icon URL instead | ||
293 | const previewUrl = buildRemoteBaseUrl(options.video, join(STATIC_PATHS.PREVIEWS, options.video.getPreview().filename)) | ||
294 | const previewModel = createPlaceholderThumbnail(previewUrl, options.video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | ||
295 | await options.video.addAndSaveThumbnail(previewModel, t) | ||
296 | |||
247 | { | 297 | { |
248 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject) | 298 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(options.video, options.videoObject) |
249 | const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) | 299 | const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) |
@@ -264,6 +314,29 @@ async function updateVideoFromAP (options: { | |||
264 | } | 314 | } |
265 | 315 | ||
266 | { | 316 | { |
317 | const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes( | ||
318 | options.video, | ||
319 | options.videoObject, | ||
320 | options.video.VideoFiles | ||
321 | ) | ||
322 | const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) | ||
323 | |||
324 | // Remove video files that do not exist anymore | ||
325 | const destroyTasks = options.video.VideoStreamingPlaylists | ||
326 | .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) | ||
327 | .map(f => f.destroy(sequelizeOptions)) | ||
328 | await Promise.all(destroyTasks) | ||
329 | |||
330 | // Update or add other one | ||
331 | const upsertTasks = streamingPlaylistAttributes.map(a => { | ||
332 | return VideoStreamingPlaylistModel.upsert<VideoStreamingPlaylistModel>(a, { returning: true, transaction: t }) | ||
333 | .then(([ streamingPlaylist ]) => streamingPlaylist) | ||
334 | }) | ||
335 | |||
336 | options.video.VideoStreamingPlaylists = await Promise.all(upsertTasks) | ||
337 | } | ||
338 | |||
339 | { | ||
267 | // Update Tags | 340 | // Update Tags |
268 | const tags = options.videoObject.tag.map(tag => tag.name) | 341 | const tags = options.videoObject.tag.map(tag => tag.name) |
269 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 342 | const tagInstances = await TagModel.findOrCreateTags(tags, t) |
@@ -296,12 +369,6 @@ async function updateVideoFromAP (options: { | |||
296 | logger.debug('Cannot update the remote video.', { err }) | 369 | logger.debug('Cannot update the remote video.', { err }) |
297 | throw err | 370 | throw err |
298 | } | 371 | } |
299 | |||
300 | try { | ||
301 | await generateThumbnailFromUrl(options.video, options.videoObject.icon) | ||
302 | } catch (err) { | ||
303 | logger.warn('Cannot generate thumbnail of %s.', options.videoObject.id, { err }) | ||
304 | } | ||
305 | } | 372 | } |
306 | 373 | ||
307 | async function refreshVideoIfNeeded (options: { | 374 | async function refreshVideoIfNeeded (options: { |
@@ -361,29 +428,55 @@ export { | |||
361 | getOrCreateVideoAndAccountAndChannel, | 428 | getOrCreateVideoAndAccountAndChannel, |
362 | fetchRemoteVideoStaticFile, | 429 | fetchRemoteVideoStaticFile, |
363 | fetchRemoteVideoDescription, | 430 | fetchRemoteVideoDescription, |
364 | generateThumbnailFromUrl, | ||
365 | getOrCreateVideoChannelFromVideoObject | 431 | getOrCreateVideoChannelFromVideoObject |
366 | } | 432 | } |
367 | 433 | ||
368 | // --------------------------------------------------------------------------- | 434 | // --------------------------------------------------------------------------- |
369 | 435 | ||
370 | function isActivityVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject { | 436 | function isAPVideoUrlObject (url: ActivityUrlObject): url is ActivityVideoUrlObject { |
371 | const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) | 437 | const mimeTypes = Object.keys(MIMETYPES.VIDEO.MIMETYPE_EXT) |
372 | 438 | ||
373 | const urlMediaType = url.mediaType || url.mimeType | 439 | const urlMediaType = url.mediaType || url.mimeType |
374 | return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/') | 440 | return mimeTypes.indexOf(urlMediaType) !== -1 && urlMediaType.startsWith('video/') |
375 | } | 441 | } |
376 | 442 | ||
443 | function isAPStreamingPlaylistUrlObject (url: ActivityUrlObject): url is ActivityPlaylistUrlObject { | ||
444 | const urlMediaType = url.mediaType || url.mimeType | ||
445 | |||
446 | return urlMediaType === 'application/x-mpegURL' | ||
447 | } | ||
448 | |||
449 | function isAPPlaylistSegmentHashesUrlObject (tag: any): tag is ActivityPlaylistSegmentHashesObject { | ||
450 | const urlMediaType = tag.mediaType || tag.mimeType | ||
451 | |||
452 | return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json' | ||
453 | } | ||
454 | |||
377 | async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { | 455 | async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { |
378 | logger.debug('Adding remote video %s.', videoObject.id) | 456 | logger.debug('Adding remote video %s.', videoObject.id) |
379 | 457 | ||
458 | const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) | ||
459 | const video = VideoModel.build(videoData) | ||
460 | |||
461 | const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | ||
462 | |||
463 | let thumbnailModel: ThumbnailModel | ||
464 | if (waitThumbnail === true) { | ||
465 | thumbnailModel = await promiseThumbnail | ||
466 | } | ||
467 | |||
380 | const videoCreated: VideoModel = await sequelizeTypescript.transaction(async t => { | 468 | const videoCreated: VideoModel = await sequelizeTypescript.transaction(async t => { |
381 | const sequelizeOptions = { transaction: t } | 469 | const sequelizeOptions = { transaction: t } |
382 | 470 | ||
383 | const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) | ||
384 | const video = VideoModel.build(videoData) | ||
385 | |||
386 | const videoCreated = await video.save(sequelizeOptions) | 471 | const videoCreated = await video.save(sequelizeOptions) |
472 | videoCreated.VideoChannel = channelActor.VideoChannel | ||
473 | |||
474 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | ||
475 | |||
476 | // FIXME: use icon URL instead | ||
477 | const previewUrl = buildRemoteBaseUrl(videoCreated, join(STATIC_PATHS.PREVIEWS, video.generatePreviewName())) | ||
478 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | ||
479 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(previewModel, t) | ||
387 | 480 | ||
388 | // Process files | 481 | // Process files |
389 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject) | 482 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoCreated, videoObject) |
@@ -392,10 +485,16 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
392 | } | 485 | } |
393 | 486 | ||
394 | const videoFilePromises = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t })) | 487 | const videoFilePromises = videoFileAttributes.map(f => VideoFileModel.create(f, { transaction: t })) |
395 | await Promise.all(videoFilePromises) | 488 | const videoFiles = await Promise.all(videoFilePromises) |
489 | |||
490 | const videoStreamingPlaylists = streamingPlaylistActivityUrlToDBAttributes(videoCreated, videoObject, videoFiles) | ||
491 | const playlistPromises = videoStreamingPlaylists.map(p => VideoStreamingPlaylistModel.create(p, { transaction: t })) | ||
492 | await Promise.all(playlistPromises) | ||
396 | 493 | ||
397 | // Process tags | 494 | // Process tags |
398 | const tags = videoObject.tag.map(t => t.name) | 495 | const tags = videoObject.tag |
496 | .filter(t => t.type === 'Hashtag') | ||
497 | .map(t => t.name) | ||
399 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 498 | const tagInstances = await TagModel.findOrCreateTags(tags, t) |
400 | await videoCreated.$set('Tags', tagInstances, sequelizeOptions) | 499 | await videoCreated.$set('Tags', tagInstances, sequelizeOptions) |
401 | 500 | ||
@@ -407,14 +506,16 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
407 | 506 | ||
408 | logger.info('Remote video with uuid %s inserted.', videoObject.uuid) | 507 | logger.info('Remote video with uuid %s inserted.', videoObject.uuid) |
409 | 508 | ||
410 | videoCreated.VideoChannel = channelActor.VideoChannel | ||
411 | return videoCreated | 509 | return videoCreated |
412 | }) | 510 | }) |
413 | 511 | ||
414 | const p = generateThumbnailFromUrl(videoCreated, videoObject.icon) | 512 | if (waitThumbnail === false) { |
415 | .catch(err => logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err })) | 513 | promiseThumbnail.then(thumbnailModel => { |
514 | thumbnailModel = videoCreated.id | ||
416 | 515 | ||
417 | if (waitThumbnail === true) await p | 516 | return thumbnailModel.save() |
517 | }) | ||
518 | } | ||
418 | 519 | ||
419 | return videoCreated | 520 | return videoCreated |
420 | } | 521 | } |
@@ -456,12 +557,14 @@ async function videoActivityObjectToDBAttributes ( | |||
456 | support, | 557 | support, |
457 | nsfw: videoObject.sensitive, | 558 | nsfw: videoObject.sensitive, |
458 | commentsEnabled: videoObject.commentsEnabled, | 559 | commentsEnabled: videoObject.commentsEnabled, |
560 | downloadEnabled: videoObject.downloadEnabled, | ||
459 | waitTranscoding: videoObject.waitTranscoding, | 561 | waitTranscoding: videoObject.waitTranscoding, |
460 | state: videoObject.state, | 562 | state: videoObject.state, |
461 | channelId: videoChannel.id, | 563 | channelId: videoChannel.id, |
462 | duration: parseInt(duration, 10), | 564 | duration: parseInt(duration, 10), |
463 | createdAt: new Date(videoObject.published), | 565 | createdAt: new Date(videoObject.published), |
464 | publishedAt: new Date(videoObject.published), | 566 | publishedAt: new Date(videoObject.published), |
567 | originallyPublishedAt: videoObject.originallyPublishedAt ? new Date(videoObject.originallyPublishedAt) : null, | ||
465 | // FIXME: updatedAt does not seems to be considered by Sequelize | 568 | // FIXME: updatedAt does not seems to be considered by Sequelize |
466 | updatedAt: new Date(videoObject.updated), | 569 | updatedAt: new Date(videoObject.updated), |
467 | views: videoObject.views, | 570 | views: videoObject.views, |
@@ -473,13 +576,13 @@ async function videoActivityObjectToDBAttributes ( | |||
473 | } | 576 | } |
474 | 577 | ||
475 | function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) { | 578 | function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) { |
476 | const fileUrls = videoObject.url.filter(u => isActivityVideoUrlObject(u)) as ActivityVideoUrlObject[] | 579 | const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] |
477 | 580 | ||
478 | if (fileUrls.length === 0) { | 581 | if (fileUrls.length === 0) { |
479 | throw new Error('Cannot find video files for ' + video.url) | 582 | throw new Error('Cannot find video files for ' + video.url) |
480 | } | 583 | } |
481 | 584 | ||
482 | const attributes: VideoFileModel[] = [] | 585 | const attributes: FilteredModelAttributes<VideoFileModel>[] = [] |
483 | for (const fileUrl of fileUrls) { | 586 | for (const fileUrl of fileUrls) { |
484 | // Fetch associated magnet uri | 587 | // Fetch associated magnet uri |
485 | const magnet = videoObject.url.find(u => { | 588 | const magnet = videoObject.url.find(u => { |
@@ -502,7 +605,38 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid | |||
502 | size: fileUrl.size, | 605 | size: fileUrl.size, |
503 | videoId: video.id, | 606 | videoId: video.id, |
504 | fps: fileUrl.fps || -1 | 607 | fps: fileUrl.fps || -1 |
505 | } as VideoFileModel | 608 | } |
609 | |||
610 | attributes.push(attribute) | ||
611 | } | ||
612 | |||
613 | return attributes | ||
614 | } | ||
615 | |||
616 | function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) { | ||
617 | const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] | ||
618 | if (playlistUrls.length === 0) return [] | ||
619 | |||
620 | const attributes: FilteredModelAttributes<VideoStreamingPlaylistModel>[] = [] | ||
621 | for (const playlistUrlObject of playlistUrls) { | ||
622 | const segmentsSha256UrlObject = playlistUrlObject.tag | ||
623 | .find(t => { | ||
624 | return isAPPlaylistSegmentHashesUrlObject(t) | ||
625 | }) as ActivityPlaylistSegmentHashesObject | ||
626 | if (!segmentsSha256UrlObject) { | ||
627 | logger.warn('No segment sha256 URL found in AP playlist object.', { playlistUrl: playlistUrlObject }) | ||
628 | continue | ||
629 | } | ||
630 | |||
631 | const attribute = { | ||
632 | type: VideoStreamingPlaylistType.HLS, | ||
633 | playlistUrl: playlistUrlObject.href, | ||
634 | segmentsSha256Url: segmentsSha256UrlObject.href, | ||
635 | p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrlObject.href, videoFiles), | ||
636 | p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION, | ||
637 | videoId: video.id | ||
638 | } | ||
639 | |||
506 | attributes.push(attribute) | 640 | attributes.push(attribute) |
507 | } | 641 | } |
508 | 642 | ||
diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts index 021426a1a..09b4e38ca 100644 --- a/server/lib/avatar.ts +++ b/server/lib/avatar.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import 'multer' | 1 | import 'multer' |
2 | import { sendUpdateActor } from './activitypub/send' | 2 | import { sendUpdateActor } from './activitypub/send' |
3 | import { AVATARS_SIZE, CONFIG, sequelizeTypescript } from '../initializers' | 3 | import { AVATARS_SIZE } from '../initializers/constants' |
4 | import { updateActorAvatarInstance } from './activitypub' | 4 | import { updateActorAvatarInstance } from './activitypub' |
5 | import { processImage } from '../helpers/image-utils' | 5 | import { processImage } from '../helpers/image-utils' |
6 | import { AccountModel } from '../models/account/account' | 6 | import { AccountModel } from '../models/account/account' |
@@ -8,12 +8,14 @@ import { VideoChannelModel } from '../models/video/video-channel' | |||
8 | import { extname, join } from 'path' | 8 | import { extname, join } from 'path' |
9 | import { retryTransactionWrapper } from '../helpers/database-utils' | 9 | import { retryTransactionWrapper } from '../helpers/database-utils' |
10 | import * as uuidv4 from 'uuid/v4' | 10 | import * as uuidv4 from 'uuid/v4' |
11 | import { CONFIG } from '../initializers/config' | ||
12 | import { sequelizeTypescript } from '../initializers/database' | ||
11 | 13 | ||
12 | async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) { | 14 | async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) { |
13 | const extension = extname(avatarPhysicalFile.filename) | 15 | const extension = extname(avatarPhysicalFile.filename) |
14 | const avatarName = uuidv4() + extension | 16 | const avatarName = uuidv4() + extension |
15 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) | 17 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) |
16 | await processImage(avatarPhysicalFile, destination, AVATARS_SIZE) | 18 | await processImage(avatarPhysicalFile.path, destination, AVATARS_SIZE) |
17 | 19 | ||
18 | return retryTransactionWrapper(() => { | 20 | return retryTransactionWrapper(() => { |
19 | return sequelizeTypescript.transaction(async t => { | 21 | return sequelizeTypescript.transaction(async t => { |
diff --git a/server/lib/cache/abstract-video-static-file-cache.ts b/server/lib/cache/abstract-video-static-file-cache.ts deleted file mode 100644 index 7512f2b9d..000000000 --- a/server/lib/cache/abstract-video-static-file-cache.ts +++ /dev/null | |||
@@ -1,52 +0,0 @@ | |||
1 | import * as AsyncLRU from 'async-lru' | ||
2 | import { createWriteStream, remove } from 'fs-extra' | ||
3 | import { logger } from '../../helpers/logger' | ||
4 | import { VideoModel } from '../../models/video/video' | ||
5 | import { fetchRemoteVideoStaticFile } from '../activitypub' | ||
6 | |||
7 | export abstract class AbstractVideoStaticFileCache <T> { | ||
8 | |||
9 | protected lru | ||
10 | |||
11 | abstract getFilePath (params: T): Promise<string> | ||
12 | |||
13 | // Load and save the remote file, then return the local path from filesystem | ||
14 | protected abstract loadRemoteFile (key: string): Promise<string> | ||
15 | |||
16 | init (max: number, maxAge: number) { | ||
17 | this.lru = new AsyncLRU({ | ||
18 | max, | ||
19 | maxAge, | ||
20 | load: (key, cb) => { | ||
21 | this.loadRemoteFile(key) | ||
22 | .then(res => cb(null, res)) | ||
23 | .catch(err => cb(err)) | ||
24 | } | ||
25 | }) | ||
26 | |||
27 | this.lru.on('evict', (obj: { key: string, value: string }) => { | ||
28 | remove(obj.value) | ||
29 | .then(() => logger.debug('%s evicted from %s', obj.value, this.constructor.name)) | ||
30 | }) | ||
31 | } | ||
32 | |||
33 | protected loadFromLRU (key: string) { | ||
34 | return new Promise<string>((res, rej) => { | ||
35 | this.lru.get(key, (err, value) => { | ||
36 | err ? rej(err) : res(value) | ||
37 | }) | ||
38 | }) | ||
39 | } | ||
40 | |||
41 | protected saveRemoteVideoFileAndReturnPath (video: VideoModel, remoteStaticPath: string, destPath: string) { | ||
42 | return new Promise<string>((res, rej) => { | ||
43 | const req = fetchRemoteVideoStaticFile(video, remoteStaticPath, rej) | ||
44 | |||
45 | const stream = createWriteStream(destPath) | ||
46 | |||
47 | req.pipe(stream) | ||
48 | .on('error', (err) => rej(err)) | ||
49 | .on('finish', () => res(destPath)) | ||
50 | }) | ||
51 | } | ||
52 | } | ||
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index b2c376e20..516827a05 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as Bluebird from 'bluebird' | ||
3 | import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' | 2 | import { buildFileLocale, getDefaultLocale, is18nLocale, POSSIBLE_LOCALES } from '../../shared/models/i18n/i18n' |
4 | import { CONFIG, CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE } from '../initializers' | 3 | import { CUSTOM_HTML_TAG_COMMENTS, EMBED_SIZE, WEBSERVER } from '../initializers/constants' |
5 | import { join } from 'path' | 4 | import { join } from 'path' |
6 | import { escapeHTML } from '../helpers/core-utils' | 5 | import { escapeHTML } from '../helpers/core-utils' |
7 | import { VideoModel } from '../models/video/video' | 6 | import { VideoModel } from '../models/video/video' |
@@ -9,10 +8,14 @@ import * as validator from 'validator' | |||
9 | import { VideoPrivacy } from '../../shared/models/videos' | 8 | import { VideoPrivacy } from '../../shared/models/videos' |
10 | import { readFile } from 'fs-extra' | 9 | import { readFile } from 'fs-extra' |
11 | import { getActivityStreamDuration } from '../models/video/video-format-utils' | 10 | import { getActivityStreamDuration } from '../models/video/video-format-utils' |
11 | import { AccountModel } from '../models/account/account' | ||
12 | import { VideoChannelModel } from '../models/video/video-channel' | ||
13 | import * as Bluebird from 'bluebird' | ||
14 | import { CONFIG } from '../initializers/config' | ||
12 | 15 | ||
13 | export class ClientHtml { | 16 | export class ClientHtml { |
14 | 17 | ||
15 | private static htmlCache: { [path: string]: string } = {} | 18 | private static htmlCache: { [ path: string ]: string } = {} |
16 | 19 | ||
17 | static invalidCache () { | 20 | static invalidCache () { |
18 | ClientHtml.htmlCache = {} | 21 | ClientHtml.htmlCache = {} |
@@ -28,18 +31,14 @@ export class ClientHtml { | |||
28 | } | 31 | } |
29 | 32 | ||
30 | static async getWatchHTMLPage (videoId: string, req: express.Request, res: express.Response) { | 33 | static async getWatchHTMLPage (videoId: string, req: express.Request, res: express.Response) { |
31 | let videoPromise: Bluebird<VideoModel> | ||
32 | |||
33 | // Let Angular application handle errors | 34 | // Let Angular application handle errors |
34 | if (validator.isInt(videoId) || validator.isUUID(videoId, 4)) { | 35 | if (!validator.isInt(videoId) && !validator.isUUID(videoId, 4)) { |
35 | videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | ||
36 | } else { | ||
37 | return ClientHtml.getIndexHTML(req, res) | 36 | return ClientHtml.getIndexHTML(req, res) |
38 | } | 37 | } |
39 | 38 | ||
40 | const [ html, video ] = await Promise.all([ | 39 | const [ html, video ] = await Promise.all([ |
41 | ClientHtml.getIndexHTML(req, res), | 40 | ClientHtml.getIndexHTML(req, res), |
42 | videoPromise | 41 | VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) |
43 | ]) | 42 | ]) |
44 | 43 | ||
45 | // Let Angular application handle errors | 44 | // Let Angular application handle errors |
@@ -49,14 +48,44 @@ export class ClientHtml { | |||
49 | 48 | ||
50 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) | 49 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(video.name)) |
51 | customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description)) | 50 | customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(video.description)) |
52 | customHtml = ClientHtml.addOpenGraphAndOEmbedTags(customHtml, video) | 51 | customHtml = ClientHtml.addVideoOpenGraphAndOEmbedTags(customHtml, video) |
52 | |||
53 | return customHtml | ||
54 | } | ||
55 | |||
56 | static async getAccountHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { | ||
57 | return this.getAccountOrChannelHTMLPage(() => AccountModel.loadByNameWithHost(nameWithHost), req, res) | ||
58 | } | ||
59 | |||
60 | static async getVideoChannelHTMLPage (nameWithHost: string, req: express.Request, res: express.Response) { | ||
61 | return this.getAccountOrChannelHTMLPage(() => VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithHost), req, res) | ||
62 | } | ||
63 | |||
64 | private static async getAccountOrChannelHTMLPage ( | ||
65 | loader: () => Bluebird<AccountModel | VideoChannelModel>, | ||
66 | req: express.Request, | ||
67 | res: express.Response | ||
68 | ) { | ||
69 | const [ html, entity ] = await Promise.all([ | ||
70 | ClientHtml.getIndexHTML(req, res), | ||
71 | loader() | ||
72 | ]) | ||
73 | |||
74 | // Let Angular application handle errors | ||
75 | if (!entity) { | ||
76 | return ClientHtml.getIndexHTML(req, res) | ||
77 | } | ||
78 | |||
79 | let customHtml = ClientHtml.addTitleTag(html, escapeHTML(entity.getDisplayName())) | ||
80 | customHtml = ClientHtml.addDescriptionTag(customHtml, escapeHTML(entity.description)) | ||
81 | customHtml = ClientHtml.addAccountOrChannelMetaTags(customHtml, entity) | ||
53 | 82 | ||
54 | return customHtml | 83 | return customHtml |
55 | } | 84 | } |
56 | 85 | ||
57 | private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { | 86 | private static async getIndexHTML (req: express.Request, res: express.Response, paramLang?: string) { |
58 | const path = ClientHtml.getIndexPath(req, res, paramLang) | 87 | const path = ClientHtml.getIndexPath(req, res, paramLang) |
59 | if (ClientHtml.htmlCache[path]) return ClientHtml.htmlCache[path] | 88 | if (ClientHtml.htmlCache[ path ]) return ClientHtml.htmlCache[ path ] |
60 | 89 | ||
61 | const buffer = await readFile(path) | 90 | const buffer = await readFile(path) |
62 | 91 | ||
@@ -64,7 +93,7 @@ export class ClientHtml { | |||
64 | 93 | ||
65 | html = ClientHtml.addCustomCSS(html) | 94 | html = ClientHtml.addCustomCSS(html) |
66 | 95 | ||
67 | ClientHtml.htmlCache[path] = html | 96 | ClientHtml.htmlCache[ path ] = html |
68 | 97 | ||
69 | return html | 98 | return html |
70 | } | 99 | } |
@@ -78,7 +107,7 @@ export class ClientHtml { | |||
78 | 107 | ||
79 | // Save locale in cookies | 108 | // Save locale in cookies |
80 | res.cookie('clientLanguage', lang, { | 109 | res.cookie('clientLanguage', lang, { |
81 | secure: CONFIG.WEBSERVER.SCHEME === 'https', | 110 | secure: WEBSERVER.SCHEME === 'https', |
82 | sameSite: true, | 111 | sameSite: true, |
83 | maxAge: 1000 * 3600 * 24 * 90 // 3 months | 112 | maxAge: 1000 * 3600 * 24 * 90 // 3 months |
84 | }) | 113 | }) |
@@ -114,13 +143,13 @@ export class ClientHtml { | |||
114 | return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag) | 143 | return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.CUSTOM_CSS, styleTag) |
115 | } | 144 | } |
116 | 145 | ||
117 | private static addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { | 146 | private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { |
118 | const previewUrl = CONFIG.WEBSERVER.URL + video.getPreviewStaticPath() | 147 | const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() |
119 | const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() | 148 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
120 | 149 | ||
121 | const videoNameEscaped = escapeHTML(video.name) | 150 | const videoNameEscaped = escapeHTML(video.name) |
122 | const videoDescriptionEscaped = escapeHTML(video.description) | 151 | const videoDescriptionEscaped = escapeHTML(video.description) |
123 | const embedUrl = CONFIG.WEBSERVER.URL + video.getEmbedStaticPath() | 152 | const embedUrl = WEBSERVER.URL + video.getEmbedStaticPath() |
124 | 153 | ||
125 | const openGraphMetaTags = { | 154 | const openGraphMetaTags = { |
126 | 'og:type': 'video', | 155 | 'og:type': 'video', |
@@ -152,7 +181,7 @@ export class ClientHtml { | |||
152 | const oembedLinkTags = [ | 181 | const oembedLinkTags = [ |
153 | { | 182 | { |
154 | type: 'application/json+oembed', | 183 | type: 'application/json+oembed', |
155 | href: CONFIG.WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(videoUrl), | 184 | href: WEBSERVER.URL + '/services/oembed?url=' + encodeURIComponent(videoUrl), |
156 | title: videoNameEscaped | 185 | title: videoNameEscaped |
157 | } | 186 | } |
158 | ] | 187 | ] |
@@ -174,7 +203,7 @@ export class ClientHtml { | |||
174 | 203 | ||
175 | // Opengraph | 204 | // Opengraph |
176 | Object.keys(openGraphMetaTags).forEach(tagName => { | 205 | Object.keys(openGraphMetaTags).forEach(tagName => { |
177 | const tagValue = openGraphMetaTags[tagName] | 206 | const tagValue = openGraphMetaTags[ tagName ] |
178 | 207 | ||
179 | tagsString += `<meta property="${tagName}" content="${tagValue}" />` | 208 | tagsString += `<meta property="${tagName}" content="${tagValue}" />` |
180 | }) | 209 | }) |
@@ -190,6 +219,17 @@ export class ClientHtml { | |||
190 | // SEO, use origin video url so Google does not index remote videos | 219 | // SEO, use origin video url so Google does not index remote videos |
191 | tagsString += `<link rel="canonical" href="${video.url}" />` | 220 | tagsString += `<link rel="canonical" href="${video.url}" />` |
192 | 221 | ||
193 | return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.OPENGRAPH_AND_OEMBED, tagsString) | 222 | return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString) |
223 | } | ||
224 | |||
225 | private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) { | ||
226 | // SEO, use origin account or channel URL | ||
227 | const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />` | ||
228 | |||
229 | return this.addOpenGraphAndOEmbedTags(htmlStringPage, metaTags) | ||
230 | } | ||
231 | |||
232 | private static addOpenGraphAndOEmbedTags (htmlStringPage: string, metaTags: string) { | ||
233 | return htmlStringPage.replace(CUSTOM_HTML_TAG_COMMENTS.META_TAGS, metaTags) | ||
194 | } | 234 | } |
195 | } | 235 | } |
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 99010f473..8c06e9751 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { createTransport, Transporter } from 'nodemailer' | 1 | import { createTransport, Transporter } from 'nodemailer' |
2 | import { isTestInstance } from '../helpers/core-utils' | 2 | import { isTestInstance } from '../helpers/core-utils' |
3 | import { bunyanLogger, logger } from '../helpers/logger' | 3 | import { bunyanLogger, logger } from '../helpers/logger' |
4 | import { CONFIG } from '../initializers' | 4 | import { CONFIG } from '../initializers/config' |
5 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
6 | import { VideoModel } from '../models/video/video' | 6 | import { VideoModel } from '../models/video/video' |
7 | import { JobQueue } from './job-queue' | 7 | import { JobQueue } from './job-queue' |
@@ -12,6 +12,16 @@ import { VideoAbuseModel } from '../models/video/video-abuse' | |||
12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
13 | import { VideoImportModel } from '../models/video/video-import' | 13 | import { VideoImportModel } from '../models/video/video-import' |
14 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | 14 | import { ActorFollowModel } from '../models/activitypub/actor-follow' |
15 | import { WEBSERVER } from '../initializers/constants' | ||
16 | |||
17 | type SendEmailOptions = { | ||
18 | to: string[] | ||
19 | subject: string | ||
20 | text: string | ||
21 | |||
22 | fromDisplayName?: string | ||
23 | replyTo?: string | ||
24 | } | ||
15 | 25 | ||
16 | class Emailer { | 26 | class Emailer { |
17 | 27 | ||
@@ -82,7 +92,7 @@ class Emailer { | |||
82 | 92 | ||
83 | addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) { | 93 | addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) { |
84 | const channelName = video.VideoChannel.getDisplayName() | 94 | const channelName = video.VideoChannel.getDisplayName() |
85 | const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() | 95 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
86 | 96 | ||
87 | const text = `Hi dear user,\n\n` + | 97 | const text = `Hi dear user,\n\n` + |
88 | `Your subscription ${channelName} just published a new video: ${video.name}` + | 98 | `Your subscription ${channelName} just published a new video: ${video.name}` + |
@@ -120,8 +130,26 @@ class Emailer { | |||
120 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 130 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
121 | } | 131 | } |
122 | 132 | ||
133 | addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) { | ||
134 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' | ||
135 | |||
136 | const text = `Hi dear admin,\n\n` + | ||
137 | `Your instance has a new follower: ${actorFollow.ActorFollower.url}${awaitingApproval}` + | ||
138 | `\n\n` + | ||
139 | `Cheers,\n` + | ||
140 | `PeerTube.` | ||
141 | |||
142 | const emailPayload: EmailPayload = { | ||
143 | to, | ||
144 | subject: 'New instance follower', | ||
145 | text | ||
146 | } | ||
147 | |||
148 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
149 | } | ||
150 | |||
123 | myVideoPublishedNotification (to: string[], video: VideoModel) { | 151 | myVideoPublishedNotification (to: string[], video: VideoModel) { |
124 | const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() | 152 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
125 | 153 | ||
126 | const text = `Hi dear user,\n\n` + | 154 | const text = `Hi dear user,\n\n` + |
127 | `Your video ${video.name} has been published.` + | 155 | `Your video ${video.name} has been published.` + |
@@ -141,7 +169,7 @@ class Emailer { | |||
141 | } | 169 | } |
142 | 170 | ||
143 | myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) { | 171 | myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) { |
144 | const videoUrl = CONFIG.WEBSERVER.URL + videoImport.Video.getWatchStaticPath() | 172 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() |
145 | 173 | ||
146 | const text = `Hi dear user,\n\n` + | 174 | const text = `Hi dear user,\n\n` + |
147 | `Your video import ${videoImport.getTargetIdentifier()} is finished.` + | 175 | `Your video import ${videoImport.getTargetIdentifier()} is finished.` + |
@@ -161,7 +189,7 @@ class Emailer { | |||
161 | } | 189 | } |
162 | 190 | ||
163 | myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) { | 191 | myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) { |
164 | const importUrl = CONFIG.WEBSERVER.URL + '/my-account/video-imports' | 192 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' |
165 | 193 | ||
166 | const text = `Hi dear user,\n\n` + | 194 | const text = `Hi dear user,\n\n` + |
167 | `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + | 195 | `Your video import ${videoImport.getTargetIdentifier()} encountered an error.` + |
@@ -183,7 +211,7 @@ class Emailer { | |||
183 | addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { | 211 | addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { |
184 | const accountName = comment.Account.getDisplayName() | 212 | const accountName = comment.Account.getDisplayName() |
185 | const video = comment.Video | 213 | const video = comment.Video |
186 | const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath() | 214 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
187 | 215 | ||
188 | const text = `Hi dear user,\n\n` + | 216 | const text = `Hi dear user,\n\n` + |
189 | `A new comment has been posted by ${accountName} on your video ${video.name}` + | 217 | `A new comment has been posted by ${accountName} on your video ${video.name}` + |
@@ -205,7 +233,7 @@ class Emailer { | |||
205 | addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) { | 233 | addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) { |
206 | const accountName = comment.Account.getDisplayName() | 234 | const accountName = comment.Account.getDisplayName() |
207 | const video = comment.Video | 235 | const video = comment.Video |
208 | const commentUrl = CONFIG.WEBSERVER.URL + comment.getCommentStaticPath() | 236 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
209 | 237 | ||
210 | const text = `Hi dear user,\n\n` + | 238 | const text = `Hi dear user,\n\n` + |
211 | `${accountName} mentioned you on video ${video.name}` + | 239 | `${accountName} mentioned you on video ${video.name}` + |
@@ -225,10 +253,10 @@ class Emailer { | |||
225 | } | 253 | } |
226 | 254 | ||
227 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) { | 255 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) { |
228 | const videoUrl = CONFIG.WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() | 256 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() |
229 | 257 | ||
230 | const text = `Hi,\n\n` + | 258 | const text = `Hi,\n\n` + |
231 | `${CONFIG.WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + | 259 | `${WEBSERVER.HOST} received an abuse for the following video ${videoUrl}\n\n` + |
232 | `Cheers,\n` + | 260 | `Cheers,\n` + |
233 | `PeerTube.` | 261 | `PeerTube.` |
234 | 262 | ||
@@ -241,15 +269,38 @@ class Emailer { | |||
241 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 269 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
242 | } | 270 | } |
243 | 271 | ||
272 | addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) { | ||
273 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' | ||
274 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | ||
275 | |||
276 | const text = `Hi,\n\n` + | ||
277 | `A recently added video was auto-blacklisted and requires moderator review before publishing.` + | ||
278 | `\n\n` + | ||
279 | `You can view it and take appropriate action on ${videoUrl}` + | ||
280 | `\n\n` + | ||
281 | `A full list of auto-blacklisted videos can be reviewed here: ${VIDEO_AUTO_BLACKLIST_URL}` + | ||
282 | `\n\n` + | ||
283 | `Cheers,\n` + | ||
284 | `PeerTube.` | ||
285 | |||
286 | const emailPayload: EmailPayload = { | ||
287 | to, | ||
288 | subject: '[PeerTube] An auto-blacklisted video is awaiting review', | ||
289 | text | ||
290 | } | ||
291 | |||
292 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
293 | } | ||
294 | |||
244 | addNewUserRegistrationNotification (to: string[], user: UserModel) { | 295 | addNewUserRegistrationNotification (to: string[], user: UserModel) { |
245 | const text = `Hi,\n\n` + | 296 | const text = `Hi,\n\n` + |
246 | `User ${user.username} just registered on ${CONFIG.WEBSERVER.HOST} PeerTube instance.\n\n` + | 297 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + |
247 | `Cheers,\n` + | 298 | `Cheers,\n` + |
248 | `PeerTube.` | 299 | `PeerTube.` |
249 | 300 | ||
250 | const emailPayload: EmailPayload = { | 301 | const emailPayload: EmailPayload = { |
251 | to, | 302 | to, |
252 | subject: '[PeerTube] New user registration on ' + CONFIG.WEBSERVER.HOST, | 303 | subject: '[PeerTube] New user registration on ' + WEBSERVER.HOST, |
253 | text | 304 | text |
254 | } | 305 | } |
255 | 306 | ||
@@ -258,10 +309,10 @@ class Emailer { | |||
258 | 309 | ||
259 | addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) { | 310 | addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) { |
260 | const videoName = videoBlacklist.Video.name | 311 | const videoName = videoBlacklist.Video.name |
261 | const videoUrl = CONFIG.WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() | 312 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
262 | 313 | ||
263 | const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : '' | 314 | const reasonString = videoBlacklist.reason ? ` for the following reason: ${videoBlacklist.reason}` : '' |
264 | const blockedString = `Your video ${videoName} (${videoUrl} on ${CONFIG.WEBSERVER.HOST} has been blacklisted${reasonString}.` | 315 | const blockedString = `Your video ${videoName} (${videoUrl} on ${WEBSERVER.HOST} has been blacklisted${reasonString}.` |
265 | 316 | ||
266 | const text = 'Hi,\n\n' + | 317 | const text = 'Hi,\n\n' + |
267 | blockedString + | 318 | blockedString + |
@@ -279,10 +330,10 @@ class Emailer { | |||
279 | } | 330 | } |
280 | 331 | ||
281 | addVideoUnblacklistNotification (to: string[], video: VideoModel) { | 332 | addVideoUnblacklistNotification (to: string[], video: VideoModel) { |
282 | const videoUrl = CONFIG.WEBSERVER.URL + video.getWatchStaticPath() | 333 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
283 | 334 | ||
284 | const text = 'Hi,\n\n' + | 335 | const text = 'Hi,\n\n' + |
285 | `Your video ${video.name} (${videoUrl}) on ${CONFIG.WEBSERVER.HOST} has been unblacklisted.` + | 336 | `Your video ${video.name} (${videoUrl}) on ${WEBSERVER.HOST} has been unblacklisted.` + |
286 | '\n\n' + | 337 | '\n\n' + |
287 | 'Cheers,\n' + | 338 | 'Cheers,\n' + |
288 | `PeerTube.` | 339 | `PeerTube.` |
@@ -296,9 +347,9 @@ class Emailer { | |||
296 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 347 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
297 | } | 348 | } |
298 | 349 | ||
299 | addForgetPasswordEmailJob (to: string, resetPasswordUrl: string) { | 350 | addPasswordResetEmailJob (to: string, resetPasswordUrl: string) { |
300 | const text = `Hi dear user,\n\n` + | 351 | const text = `Hi dear user,\n\n` + |
301 | `It seems you forgot your password on ${CONFIG.WEBSERVER.HOST}! ` + | 352 | `A reset password procedure for your account ${to} has been requested on ${WEBSERVER.HOST} ` + |
302 | `Please follow this link to reset it: ${resetPasswordUrl}\n\n` + | 353 | `Please follow this link to reset it: ${resetPasswordUrl}\n\n` + |
303 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 354 | `If you are not the person who initiated this request, please ignore this email.\n\n` + |
304 | `Cheers,\n` + | 355 | `Cheers,\n` + |
@@ -315,7 +366,7 @@ class Emailer { | |||
315 | 366 | ||
316 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { | 367 | addVerifyEmailJob (to: string, verifyEmailUrl: string) { |
317 | const text = `Welcome to PeerTube,\n\n` + | 368 | const text = `Welcome to PeerTube,\n\n` + |
318 | `To start using PeerTube on ${CONFIG.WEBSERVER.HOST} you must verify your email! ` + | 369 | `To start using PeerTube on ${WEBSERVER.HOST} you must verify your email! ` + |
319 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + | 370 | `Please follow this link to verify this email belongs to you: ${verifyEmailUrl}\n\n` + |
320 | `If you are not the person who initiated this request, please ignore this email.\n\n` + | 371 | `If you are not the person who initiated this request, please ignore this email.\n\n` + |
321 | `Cheers,\n` + | 372 | `Cheers,\n` + |
@@ -333,7 +384,7 @@ class Emailer { | |||
333 | addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { | 384 | addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { |
334 | const reasonString = reason ? ` for the following reason: ${reason}` : '' | 385 | const reasonString = reason ? ` for the following reason: ${reason}` : '' |
335 | const blockedWord = blocked ? 'blocked' : 'unblocked' | 386 | const blockedWord = blocked ? 'blocked' : 'unblocked' |
336 | const blockedString = `Your account ${user.username} on ${CONFIG.WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` | 387 | const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` |
337 | 388 | ||
338 | const text = 'Hi,\n\n' + | 389 | const text = 'Hi,\n\n' + |
339 | blockedString + | 390 | blockedString + |
@@ -378,7 +429,7 @@ class Emailer { | |||
378 | 429 | ||
379 | const fromDisplayName = options.fromDisplayName | 430 | const fromDisplayName = options.fromDisplayName |
380 | ? options.fromDisplayName | 431 | ? options.fromDisplayName |
381 | : CONFIG.WEBSERVER.HOST | 432 | : WEBSERVER.HOST |
382 | 433 | ||
383 | return this.transporter.sendMail({ | 434 | return this.transporter.sendMail({ |
384 | from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`, | 435 | from: `"${fromDisplayName}" <${CONFIG.SMTP.FROM_ADDRESS}>`, |
@@ -402,5 +453,6 @@ class Emailer { | |||
402 | // --------------------------------------------------------------------------- | 453 | // --------------------------------------------------------------------------- |
403 | 454 | ||
404 | export { | 455 | export { |
405 | Emailer | 456 | Emailer, |
457 | SendEmailOptions | ||
406 | } | 458 | } |
diff --git a/server/lib/files-cache/abstract-video-static-file-cache.ts b/server/lib/files-cache/abstract-video-static-file-cache.ts new file mode 100644 index 000000000..1908cfb06 --- /dev/null +++ b/server/lib/files-cache/abstract-video-static-file-cache.ts | |||
@@ -0,0 +1,30 @@ | |||
1 | import { remove } from 'fs-extra' | ||
2 | import { logger } from '../../helpers/logger' | ||
3 | import * as memoizee from 'memoizee' | ||
4 | |||
5 | type GetFilePathResult = { isOwned: boolean, path: string } | undefined | ||
6 | |||
7 | export abstract class AbstractVideoStaticFileCache <T> { | ||
8 | |||
9 | getFilePath: (params: T) => Promise<GetFilePathResult> | ||
10 | |||
11 | abstract getFilePathImpl (params: T): Promise<GetFilePathResult> | ||
12 | |||
13 | // Load and save the remote file, then return the local path from filesystem | ||
14 | protected abstract loadRemoteFile (key: string): Promise<GetFilePathResult> | ||
15 | |||
16 | init (max: number, maxAge: number) { | ||
17 | this.getFilePath = memoizee(this.getFilePathImpl, { | ||
18 | maxAge, | ||
19 | max, | ||
20 | promise: true, | ||
21 | dispose: (result: GetFilePathResult) => { | ||
22 | if (result.isOwned !== true) { | ||
23 | remove(result.path) | ||
24 | .then(() => logger.debug('%s removed from %s', result.path, this.constructor.name)) | ||
25 | .catch(err => logger.error('Cannot remove %s from cache %s.', result.path, this.constructor.name, { err })) | ||
26 | } | ||
27 | } | ||
28 | }) | ||
29 | } | ||
30 | } | ||
diff --git a/server/lib/cache/actor-follow-score-cache.ts b/server/lib/files-cache/actor-follow-score-cache.ts index d070bde09..5f8ee806f 100644 --- a/server/lib/cache/actor-follow-score-cache.ts +++ b/server/lib/files-cache/actor-follow-score-cache.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { ACTOR_FOLLOW_SCORE } from '../../initializers' | 1 | import { ACTOR_FOLLOW_SCORE } from '../../initializers/constants' |
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../helpers/logger' |
3 | 3 | ||
4 | // Cache follows scores, instead of writing them too often in database | 4 | // Cache follows scores, instead of writing them too often in database |
diff --git a/server/lib/cache/index.ts b/server/lib/files-cache/index.ts index e921d04a7..e921d04a7 100644 --- a/server/lib/cache/index.ts +++ b/server/lib/files-cache/index.ts | |||
diff --git a/server/lib/cache/videos-caption-cache.ts b/server/lib/files-cache/videos-caption-cache.ts index f240affbc..440c3fde8 100644 --- a/server/lib/cache/videos-caption-cache.ts +++ b/server/lib/files-cache/videos-caption-cache.ts | |||
@@ -1,8 +1,11 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { CACHE, CONFIG } from '../../initializers' | 2 | import { FILES_CACHE } from '../../initializers/constants' |
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { VideoCaptionModel } from '../../models/video/video-caption' | 4 | import { VideoCaptionModel } from '../../models/video/video-caption' |
5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 5 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
6 | import { CONFIG } from '../../initializers/config' | ||
7 | import { logger } from '../../helpers/logger' | ||
8 | import { fetchRemoteVideoStaticFile } from '../activitypub' | ||
6 | 9 | ||
7 | type GetPathParam = { videoId: string, language: string } | 10 | type GetPathParam = { videoId: string, language: string } |
8 | 11 | ||
@@ -19,17 +22,19 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | |||
19 | return this.instance || (this.instance = new this()) | 22 | return this.instance || (this.instance = new this()) |
20 | } | 23 | } |
21 | 24 | ||
22 | async getFilePath (params: GetPathParam) { | 25 | async getFilePathImpl (params: GetPathParam) { |
23 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language) | 26 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(params.videoId, params.language) |
24 | if (!videoCaption) return undefined | 27 | if (!videoCaption) return undefined |
25 | 28 | ||
26 | if (videoCaption.isOwned()) return join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) | 29 | if (videoCaption.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.CAPTIONS_DIR, videoCaption.getCaptionName()) } |
27 | 30 | ||
28 | const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language | 31 | const key = params.videoId + VideosCaptionCache.KEY_DELIMITER + params.language |
29 | return this.loadFromLRU(key) | 32 | return this.loadRemoteFile(key) |
30 | } | 33 | } |
31 | 34 | ||
32 | protected async loadRemoteFile (key: string) { | 35 | protected async loadRemoteFile (key: string) { |
36 | logger.debug('Loading remote caption file %s.', key) | ||
37 | |||
33 | const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER) | 38 | const [ videoId, language ] = key.split(VideosCaptionCache.KEY_DELIMITER) |
34 | 39 | ||
35 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language) | 40 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(videoId, language) |
@@ -41,10 +46,13 @@ class VideosCaptionCache extends AbstractVideoStaticFileCache <GetPathParam> { | |||
41 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) | 46 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(videoId) |
42 | if (!video) return undefined | 47 | if (!video) return undefined |
43 | 48 | ||
49 | // FIXME: use URL | ||
44 | const remoteStaticPath = videoCaption.getCaptionStaticPath() | 50 | const remoteStaticPath = videoCaption.getCaptionStaticPath() |
45 | const destPath = join(CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) | 51 | const destPath = join(FILES_CACHE.VIDEO_CAPTIONS.DIRECTORY, videoCaption.getCaptionName()) |
52 | |||
53 | await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) | ||
46 | 54 | ||
47 | return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath) | 55 | return { isOwned: false, path: destPath } |
48 | } | 56 | } |
49 | } | 57 | } |
50 | 58 | ||
diff --git a/server/lib/cache/videos-preview-cache.ts b/server/lib/files-cache/videos-preview-cache.ts index a5d6f5b62..14be7f24a 100644 --- a/server/lib/cache/videos-preview-cache.ts +++ b/server/lib/files-cache/videos-preview-cache.ts | |||
@@ -1,7 +1,9 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { CACHE, CONFIG, STATIC_PATHS } from '../../initializers' | 2 | import { FILES_CACHE, STATIC_PATHS } from '../../initializers/constants' |
3 | import { VideoModel } from '../../models/video/video' | 3 | import { VideoModel } from '../../models/video/video' |
4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' | 4 | import { AbstractVideoStaticFileCache } from './abstract-video-static-file-cache' |
5 | import { CONFIG } from '../../initializers/config' | ||
6 | import { fetchRemoteVideoStaticFile } from '../activitypub' | ||
5 | 7 | ||
6 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | 8 | class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { |
7 | 9 | ||
@@ -15,13 +17,13 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
15 | return this.instance || (this.instance = new this()) | 17 | return this.instance || (this.instance = new this()) |
16 | } | 18 | } |
17 | 19 | ||
18 | async getFilePath (videoUUID: string) { | 20 | async getFilePathImpl (videoUUID: string) { |
19 | const video = await VideoModel.loadByUUIDWithFile(videoUUID) | 21 | const video = await VideoModel.loadByUUIDWithFile(videoUUID) |
20 | if (!video) return undefined | 22 | if (!video) return undefined |
21 | 23 | ||
22 | if (video.isOwned()) return join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreviewName()) | 24 | if (video.isOwned()) return { isOwned: true, path: join(CONFIG.STORAGE.PREVIEWS_DIR, video.getPreview().filename) } |
23 | 25 | ||
24 | return this.loadFromLRU(videoUUID) | 26 | return this.loadRemoteFile(videoUUID) |
25 | } | 27 | } |
26 | 28 | ||
27 | protected async loadRemoteFile (key: string) { | 29 | protected async loadRemoteFile (key: string) { |
@@ -30,10 +32,13 @@ class VideosPreviewCache extends AbstractVideoStaticFileCache <string> { | |||
30 | 32 | ||
31 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') | 33 | if (video.isOwned()) throw new Error('Cannot load remote preview of owned video.') |
32 | 34 | ||
33 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreviewName()) | 35 | // FIXME: use URL |
34 | const destPath = join(CACHE.PREVIEWS.DIRECTORY, video.getPreviewName()) | 36 | const remoteStaticPath = join(STATIC_PATHS.PREVIEWS, video.getPreview().filename) |
37 | const destPath = join(FILES_CACHE.PREVIEWS.DIRECTORY, video.getPreview().filename) | ||
35 | 38 | ||
36 | return this.saveRemoteVideoFileAndReturnPath(video, remoteStaticPath, destPath) | 39 | await fetchRemoteVideoStaticFile(video, remoteStaticPath, destPath) |
40 | |||
41 | return { isOwned: false, path: destPath } | ||
37 | } | 42 | } |
38 | } | 43 | } |
39 | 44 | ||
diff --git a/server/lib/hls.ts b/server/lib/hls.ts new file mode 100644 index 000000000..98da4dcd8 --- /dev/null +++ b/server/lib/hls.ts | |||
@@ -0,0 +1,184 @@ | |||
1 | import { VideoModel } from '../models/video/video' | ||
2 | import { basename, dirname, join } from 'path' | ||
3 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' | ||
4 | import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' | ||
5 | import { getVideoFileSize } from '../helpers/ffmpeg-utils' | ||
6 | import { sha256 } from '../helpers/core-utils' | ||
7 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
8 | import { logger } from '../helpers/logger' | ||
9 | import { doRequest, doRequestAndSaveToFile } from '../helpers/requests' | ||
10 | import { generateRandomString } from '../helpers/utils' | ||
11 | import { flatten, uniq } from 'lodash' | ||
12 | import { VideoFileModel } from '../models/video/video-file' | ||
13 | import { CONFIG } from '../initializers/config' | ||
14 | import { sequelizeTypescript } from '../initializers/database' | ||
15 | |||
16 | async function updateStreamingPlaylistsInfohashesIfNeeded () { | ||
17 | const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() | ||
18 | |||
19 | // Use separate SQL queries, because we could have many videos to update | ||
20 | for (const playlist of playlistsToUpdate) { | ||
21 | await sequelizeTypescript.transaction(async t => { | ||
22 | const videoFiles = await VideoFileModel.listByStreamingPlaylist(playlist.id, t) | ||
23 | |||
24 | playlist.p2pMediaLoaderInfohashes = VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlist.playlistUrl, videoFiles) | ||
25 | playlist.p2pMediaLoaderPeerVersion = P2P_MEDIA_LOADER_PEER_VERSION | ||
26 | await playlist.save({ transaction: t }) | ||
27 | }) | ||
28 | } | ||
29 | } | ||
30 | |||
31 | async function updateMasterHLSPlaylist (video: VideoModel) { | ||
32 | const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | ||
33 | const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] | ||
34 | const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) | ||
35 | |||
36 | for (const file of video.VideoFiles) { | ||
37 | // If we did not generated a playlist for this resolution, skip | ||
38 | const filePlaylistPath = join(directory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) | ||
39 | if (await pathExists(filePlaylistPath) === false) continue | ||
40 | |||
41 | const videoFilePath = video.getVideoFilePath(file) | ||
42 | |||
43 | const size = await getVideoFileSize(videoFilePath) | ||
44 | |||
45 | const bandwidth = 'BANDWIDTH=' + video.getBandwidthBits(file) | ||
46 | const resolution = `RESOLUTION=${size.width}x${size.height}` | ||
47 | |||
48 | let line = `#EXT-X-STREAM-INF:${bandwidth},${resolution}` | ||
49 | if (file.fps) line += ',FRAME-RATE=' + file.fps | ||
50 | |||
51 | masterPlaylists.push(line) | ||
52 | masterPlaylists.push(VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) | ||
53 | } | ||
54 | |||
55 | await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') | ||
56 | } | ||
57 | |||
58 | async function updateSha256Segments (video: VideoModel) { | ||
59 | const json: { [filename: string]: { [range: string]: string } } = {} | ||
60 | |||
61 | const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | ||
62 | |||
63 | // For all the resolutions available for this video | ||
64 | for (const file of video.VideoFiles) { | ||
65 | const rangeHashes: { [range: string]: string } = {} | ||
66 | |||
67 | const videoPath = join(playlistDirectory, VideoStreamingPlaylistModel.getHlsVideoName(video.uuid, file.resolution)) | ||
68 | const playlistPath = join(playlistDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(file.resolution)) | ||
69 | |||
70 | // Maybe the playlist is not generated for this resolution yet | ||
71 | if (!await pathExists(playlistPath)) continue | ||
72 | |||
73 | const playlistContent = await readFile(playlistPath) | ||
74 | const ranges = getRangesFromPlaylist(playlistContent.toString()) | ||
75 | |||
76 | const fd = await open(videoPath, 'r') | ||
77 | for (const range of ranges) { | ||
78 | const buf = Buffer.alloc(range.length) | ||
79 | await read(fd, buf, 0, range.length, range.offset) | ||
80 | |||
81 | rangeHashes[`${range.offset}-${range.offset + range.length - 1}`] = sha256(buf) | ||
82 | } | ||
83 | await close(fd) | ||
84 | |||
85 | const videoFilename = VideoStreamingPlaylistModel.getHlsVideoName(video.uuid, file.resolution) | ||
86 | json[videoFilename] = rangeHashes | ||
87 | } | ||
88 | |||
89 | const outputPath = join(playlistDirectory, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename()) | ||
90 | await outputJSON(outputPath, json) | ||
91 | } | ||
92 | |||
93 | function getRangesFromPlaylist (playlistContent: string) { | ||
94 | const ranges: { offset: number, length: number }[] = [] | ||
95 | const lines = playlistContent.split('\n') | ||
96 | const regex = /^#EXT-X-BYTERANGE:(\d+)@(\d+)$/ | ||
97 | |||
98 | for (const line of lines) { | ||
99 | const captured = regex.exec(line) | ||
100 | |||
101 | if (captured) { | ||
102 | ranges.push({ length: parseInt(captured[1], 10), offset: parseInt(captured[2], 10) }) | ||
103 | } | ||
104 | } | ||
105 | |||
106 | return ranges | ||
107 | } | ||
108 | |||
109 | function downloadPlaylistSegments (playlistUrl: string, destinationDir: string, timeout: number) { | ||
110 | let timer | ||
111 | |||
112 | logger.info('Importing HLS playlist %s', playlistUrl) | ||
113 | |||
114 | return new Promise<string>(async (res, rej) => { | ||
115 | const tmpDirectory = join(CONFIG.STORAGE.TMP_DIR, await generateRandomString(10)) | ||
116 | |||
117 | await ensureDir(tmpDirectory) | ||
118 | |||
119 | timer = setTimeout(() => { | ||
120 | deleteTmpDirectory(tmpDirectory) | ||
121 | |||
122 | return rej(new Error('HLS download timeout.')) | ||
123 | }, timeout) | ||
124 | |||
125 | try { | ||
126 | // Fetch master playlist | ||
127 | const subPlaylistUrls = await fetchUniqUrls(playlistUrl) | ||
128 | |||
129 | const subRequests = subPlaylistUrls.map(u => fetchUniqUrls(u)) | ||
130 | const fileUrls = uniq(flatten(await Promise.all(subRequests))) | ||
131 | |||
132 | logger.debug('Will download %d HLS files.', fileUrls.length, { fileUrls }) | ||
133 | |||
134 | for (const fileUrl of fileUrls) { | ||
135 | const destPath = join(tmpDirectory, basename(fileUrl)) | ||
136 | |||
137 | const bodyKBLimit = 10 * 1000 * 1000 // 10GB | ||
138 | await doRequestAndSaveToFile({ uri: fileUrl }, destPath, bodyKBLimit) | ||
139 | } | ||
140 | |||
141 | clearTimeout(timer) | ||
142 | |||
143 | await move(tmpDirectory, destinationDir, { overwrite: true }) | ||
144 | |||
145 | return res() | ||
146 | } catch (err) { | ||
147 | deleteTmpDirectory(tmpDirectory) | ||
148 | |||
149 | return rej(err) | ||
150 | } | ||
151 | }) | ||
152 | |||
153 | function deleteTmpDirectory (directory: string) { | ||
154 | remove(directory) | ||
155 | .catch(err => logger.error('Cannot delete path on HLS download error.', { err })) | ||
156 | } | ||
157 | |||
158 | async function fetchUniqUrls (playlistUrl: string) { | ||
159 | const { body } = await doRequest<string>({ uri: playlistUrl }) | ||
160 | |||
161 | if (!body) return [] | ||
162 | |||
163 | const urls = body.split('\n') | ||
164 | .filter(line => line.endsWith('.m3u8') || line.endsWith('.mp4')) | ||
165 | .map(url => { | ||
166 | if (url.startsWith('http://') || url.startsWith('https://')) return url | ||
167 | |||
168 | return `${dirname(playlistUrl)}/${url}` | ||
169 | }) | ||
170 | |||
171 | return uniq(urls) | ||
172 | } | ||
173 | } | ||
174 | |||
175 | // --------------------------------------------------------------------------- | ||
176 | |||
177 | export { | ||
178 | updateMasterHLSPlaylist, | ||
179 | updateSha256Segments, | ||
180 | downloadPlaylistSegments, | ||
181 | updateStreamingPlaylistsInfohashesIfNeeded | ||
182 | } | ||
183 | |||
184 | // --------------------------------------------------------------------------- | ||
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts index b4d381062..b3defb617 100644 --- a/server/lib/job-queue/handlers/activitypub-follow.ts +++ b/server/lib/job-queue/handlers/activitypub-follow.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { CONFIG, REMOTE_SCHEME, sequelizeTypescript } from '../../../initializers' | 3 | import { REMOTE_SCHEME, WEBSERVER } from '../../../initializers/constants' |
4 | import { sendFollow } from '../../activitypub/send' | 4 | import { sendFollow } from '../../activitypub/send' |
5 | import { sanitizeHost } from '../../../helpers/core-utils' | 5 | import { sanitizeHost } from '../../../helpers/core-utils' |
6 | import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger' | 6 | import { loadActorUrlOrGetFromWebfinger } from '../../../helpers/webfinger' |
@@ -9,6 +9,7 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils' | |||
9 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 9 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
10 | import { ActorModel } from '../../../models/activitypub/actor' | 10 | import { ActorModel } from '../../../models/activitypub/actor' |
11 | import { Notifier } from '../../notifier' | 11 | import { Notifier } from '../../notifier' |
12 | import { sequelizeTypescript } from '../../../initializers/database' | ||
12 | 13 | ||
13 | export type ActivitypubFollowPayload = { | 14 | export type ActivitypubFollowPayload = { |
14 | followerActorId: number | 15 | followerActorId: number |
@@ -23,7 +24,7 @@ async function processActivityPubFollow (job: Bull.Job) { | |||
23 | logger.info('Processing ActivityPub follow in job %d.', job.id) | 24 | logger.info('Processing ActivityPub follow in job %d.', job.id) |
24 | 25 | ||
25 | let targetActor: ActorModel | 26 | let targetActor: ActorModel |
26 | if (!host || host === CONFIG.WEBSERVER.HOST) { | 27 | if (!host || host === WEBSERVER.HOST) { |
27 | targetActor = await ActorModel.loadLocalByName(payload.name) | 28 | targetActor = await ActorModel.loadLocalByName(payload.name) |
28 | } else { | 29 | } else { |
29 | const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) | 30 | const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) |
@@ -73,5 +74,5 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) { | |||
73 | return actorFollow | 74 | return actorFollow |
74 | }) | 75 | }) |
75 | 76 | ||
76 | if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewFollow(actorFollow) | 77 | if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollow) |
77 | } | 78 | } |
diff --git a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts index 9493945ff..0ff7b44a0 100644 --- a/server/lib/job-queue/handlers/activitypub-http-broadcast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-broadcast.ts | |||
@@ -2,10 +2,9 @@ import * as Bull from 'bull' | |||
2 | import * as Bluebird from 'bluebird' | 2 | import * as Bluebird from 'bluebird' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { doRequest } from '../../../helpers/requests' | 4 | import { doRequest } from '../../../helpers/requests' |
5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
6 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' | 5 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' |
7 | import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers' | 6 | import { BROADCAST_CONCURRENCY, JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' |
8 | import { ActorFollowScoreCache } from '../../cache' | 7 | import { ActorFollowScoreCache } from '../../files-cache' |
9 | 8 | ||
10 | export type ActivitypubHttpBroadcastPayload = { | 9 | export type ActivitypubHttpBroadcastPayload = { |
11 | uris: string[] | 10 | uris: string[] |
diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts index 67ccfa995..23d33c26f 100644 --- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts +++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts | |||
@@ -1,17 +1,24 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import * as Bluebird from 'bluebird' | ||
2 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
3 | import { processActivities } from '../../activitypub/process' | 4 | import { processActivities } from '../../activitypub/process' |
4 | import { addVideoComments } from '../../activitypub/video-comments' | 5 | import { addVideoComments } from '../../activitypub/video-comments' |
5 | import { crawlCollectionPage } from '../../activitypub/crawl' | 6 | import { crawlCollectionPage } from '../../activitypub/crawl' |
6 | import { VideoModel } from '../../../models/video/video' | 7 | import { VideoModel } from '../../../models/video/video' |
7 | import { addVideoShares, createRates } from '../../activitypub' | 8 | import { addVideoShares, createRates } from '../../activitypub' |
9 | import { createAccountPlaylists } from '../../activitypub/playlist' | ||
10 | import { AccountModel } from '../../../models/account/account' | ||
11 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | ||
12 | import { VideoShareModel } from '../../../models/video/video-share' | ||
13 | import { VideoCommentModel } from '../../../models/video/video-comment' | ||
8 | 14 | ||
9 | type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 15 | type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' |
10 | 16 | ||
11 | export type ActivitypubHttpFetcherPayload = { | 17 | export type ActivitypubHttpFetcherPayload = { |
12 | uri: string | 18 | uri: string |
13 | type: FetchType | 19 | type: FetchType |
14 | videoId?: number | 20 | videoId?: number |
21 | accountId?: number | ||
15 | } | 22 | } |
16 | 23 | ||
17 | async function processActivityPubHttpFetcher (job: Bull.Job) { | 24 | async function processActivityPubHttpFetcher (job: Bull.Job) { |
@@ -22,15 +29,26 @@ async function processActivityPubHttpFetcher (job: Bull.Job) { | |||
22 | let video: VideoModel | 29 | let video: VideoModel |
23 | if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) | 30 | if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) |
24 | 31 | ||
32 | let account: AccountModel | ||
33 | if (payload.accountId) account = await AccountModel.load(payload.accountId) | ||
34 | |||
25 | const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { | 35 | const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { |
26 | 'activity': items => processActivities(items, { outboxUrl: payload.uri }), | 36 | 'activity': items => processActivities(items, { outboxUrl: payload.uri }), |
27 | 'video-likes': items => createRates(items, video, 'like'), | 37 | 'video-likes': items => createRates(items, video, 'like'), |
28 | 'video-dislikes': items => createRates(items, video, 'dislike'), | 38 | 'video-dislikes': items => createRates(items, video, 'dislike'), |
29 | 'video-shares': items => addVideoShares(items, video), | 39 | 'video-shares': items => addVideoShares(items, video), |
30 | 'video-comments': items => addVideoComments(items, video) | 40 | 'video-comments': items => addVideoComments(items, video), |
41 | 'account-playlists': items => createAccountPlaylists(items, account) | ||
42 | } | ||
43 | |||
44 | const cleanerType: { [ id in FetchType ]?: (crawlStartDate: Date) => Bluebird<any> } = { | ||
45 | 'video-likes': crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'like' as 'like', crawlStartDate), | ||
46 | 'video-dislikes': crawlStartDate => AccountVideoRateModel.cleanOldRatesOf(video.id, 'dislike' as 'dislike', crawlStartDate), | ||
47 | 'video-shares': crawlStartDate => VideoShareModel.cleanOldSharesOf(video.id, crawlStartDate), | ||
48 | 'video-comments': crawlStartDate => VideoCommentModel.cleanOldCommentsOf(video.id, crawlStartDate) | ||
31 | } | 49 | } |
32 | 50 | ||
33 | return crawlCollectionPage(payload.uri, fetcherType[payload.type]) | 51 | return crawlCollectionPage(payload.uri, fetcherType[payload.type], cleanerType[payload.type]) |
34 | } | 52 | } |
35 | 53 | ||
36 | // --------------------------------------------------------------------------- | 54 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/job-queue/handlers/activitypub-http-unicast.ts b/server/lib/job-queue/handlers/activitypub-http-unicast.ts index 3973dcdc8..c70ce3be9 100644 --- a/server/lib/job-queue/handlers/activitypub-http-unicast.ts +++ b/server/lib/job-queue/handlers/activitypub-http-unicast.ts | |||
@@ -2,8 +2,8 @@ import * as Bull from 'bull' | |||
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { doRequest } from '../../../helpers/requests' | 3 | import { doRequest } from '../../../helpers/requests' |
4 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' | 4 | import { buildGlobalHeaders, buildSignedRequestOptions, computeBody } from './utils/activitypub-http-utils' |
5 | import { JOB_REQUEST_TIMEOUT } from '../../../initializers' | 5 | import { JOB_REQUEST_TIMEOUT } from '../../../initializers/constants' |
6 | import { ActorFollowScoreCache } from '../../cache' | 6 | import { ActorFollowScoreCache } from '../../files-cache' |
7 | 7 | ||
8 | export type ActivitypubHttpUnicastPayload = { | 8 | export type ActivitypubHttpUnicastPayload = { |
9 | uri: string | 9 | uri: string |
diff --git a/server/lib/job-queue/handlers/activitypub-refresher.ts b/server/lib/job-queue/handlers/activitypub-refresher.ts index 454b975fe..4d6c38cfa 100644 --- a/server/lib/job-queue/handlers/activitypub-refresher.ts +++ b/server/lib/job-queue/handlers/activitypub-refresher.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { fetchVideoByUrl } from '../../../helpers/video' | 3 | import { fetchVideoByUrl } from '../../../helpers/video' |
4 | import { refreshVideoIfNeeded, refreshActorIfNeeded } from '../../activitypub' | 4 | import { refreshActorIfNeeded, refreshVideoIfNeeded, refreshVideoPlaylistIfNeeded } from '../../activitypub' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
6 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
6 | 7 | ||
7 | export type RefreshPayload = { | 8 | export type RefreshPayload = { |
8 | type: 'video' | 'actor' | 9 | type: 'video' | 'video-playlist' | 'actor' |
9 | url: string | 10 | url: string |
10 | } | 11 | } |
11 | 12 | ||
@@ -15,13 +16,13 @@ async function refreshAPObject (job: Bull.Job) { | |||
15 | logger.info('Processing AP refresher in job %d for %s.', job.id, payload.url) | 16 | logger.info('Processing AP refresher in job %d for %s.', job.id, payload.url) |
16 | 17 | ||
17 | if (payload.type === 'video') return refreshVideo(payload.url) | 18 | if (payload.type === 'video') return refreshVideo(payload.url) |
19 | if (payload.type === 'video-playlist') return refreshVideoPlaylist(payload.url) | ||
18 | if (payload.type === 'actor') return refreshActor(payload.url) | 20 | if (payload.type === 'actor') return refreshActor(payload.url) |
19 | } | 21 | } |
20 | 22 | ||
21 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
22 | 24 | ||
23 | export { | 25 | export { |
24 | refreshActor, | ||
25 | refreshAPObject | 26 | refreshAPObject |
26 | } | 27 | } |
27 | 28 | ||
@@ -50,5 +51,12 @@ async function refreshActor (actorUrl: string) { | |||
50 | if (actor) { | 51 | if (actor) { |
51 | await refreshActorIfNeeded(actor, fetchType) | 52 | await refreshActorIfNeeded(actor, fetchType) |
52 | } | 53 | } |
54 | } | ||
55 | |||
56 | async function refreshVideoPlaylist (playlistUrl: string) { | ||
57 | const playlist = await VideoPlaylistModel.loadByUrlAndPopulateAccount(playlistUrl) | ||
53 | 58 | ||
59 | if (playlist) { | ||
60 | await refreshVideoPlaylistIfNeeded(playlist) | ||
61 | } | ||
54 | } | 62 | } |
diff --git a/server/lib/job-queue/handlers/email.ts b/server/lib/job-queue/handlers/email.ts index 2ba39a156..62701222c 100644 --- a/server/lib/job-queue/handlers/email.ts +++ b/server/lib/job-queue/handlers/email.ts | |||
@@ -1,15 +1,8 @@ | |||
1 | import * as Bull from 'bull' | 1 | import * as Bull from 'bull' |
2 | import { logger } from '../../../helpers/logger' | 2 | import { logger } from '../../../helpers/logger' |
3 | import { Emailer } from '../../emailer' | 3 | import { Emailer, SendEmailOptions } from '../../emailer' |
4 | 4 | ||
5 | export type EmailPayload = { | 5 | export type EmailPayload = SendEmailOptions |
6 | to: string[] | ||
7 | subject: string | ||
8 | text: string | ||
9 | |||
10 | fromDisplayName?: string | ||
11 | replyTo?: string | ||
12 | } | ||
13 | 6 | ||
14 | async function processEmail (job: Bull.Job) { | 7 | async function processEmail (job: Bull.Job) { |
15 | const payload = job.data as EmailPayload | 8 | const payload = job.data as EmailPayload |
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts index 4961d4502..cdee1f6fd 100644 --- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts +++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts | |||
@@ -2,7 +2,7 @@ import { buildSignedActivity } from '../../../../helpers/activitypub' | |||
2 | import { getServerActor } from '../../../../helpers/utils' | 2 | import { getServerActor } from '../../../../helpers/utils' |
3 | import { ActorModel } from '../../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../../models/activitypub/actor' |
4 | import { sha256 } from '../../../../helpers/core-utils' | 4 | import { sha256 } from '../../../../helpers/core-utils' |
5 | import { HTTP_SIGNATURE } from '../../../../initializers' | 5 | import { HTTP_SIGNATURE } from '../../../../initializers/constants' |
6 | 6 | ||
7 | type Payload = { body: any, signatureActorId?: number } | 7 | type Payload = { body: any, signatureActorId?: number } |
8 | 8 | ||
@@ -28,7 +28,7 @@ async function buildSignedRequestOptions (payload: Payload) { | |||
28 | actor = await getServerActor() | 28 | actor = await getServerActor() |
29 | } | 29 | } |
30 | 30 | ||
31 | const keyId = actor.getWebfingerUrl() | 31 | const keyId = actor.url |
32 | return { | 32 | return { |
33 | algorithm: HTTP_SIGNATURE.ALGORITHM, | 33 | algorithm: HTTP_SIGNATURE.ALGORITHM, |
34 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, | 34 | authorizationHeaderName: HTTP_SIGNATURE.HEADER_NAME, |
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts new file mode 100644 index 000000000..921d9a083 --- /dev/null +++ b/server/lib/job-queue/handlers/video-file-import.ts | |||
@@ -0,0 +1,78 @@ | |||
1 | import * as Bull from 'bull' | ||
2 | import { logger } from '../../../helpers/logger' | ||
3 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { publishVideoIfNeeded } from './video-transcoding' | ||
5 | import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | ||
6 | import { copy, stat } from 'fs-extra' | ||
7 | import { VideoFileModel } from '../../../models/video/video-file' | ||
8 | import { extname } from 'path' | ||
9 | |||
10 | export type VideoFileImportPayload = { | ||
11 | videoUUID: string, | ||
12 | filePath: string | ||
13 | } | ||
14 | |||
15 | async function processVideoFileImport (job: Bull.Job) { | ||
16 | const payload = job.data as VideoFileImportPayload | ||
17 | logger.info('Processing video file import in job %d.', job.id) | ||
18 | |||
19 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID) | ||
20 | // No video, maybe deleted? | ||
21 | if (!video) { | ||
22 | logger.info('Do not process job %d, video does not exist.', job.id) | ||
23 | return undefined | ||
24 | } | ||
25 | |||
26 | await updateVideoFile(video, payload.filePath) | ||
27 | |||
28 | await publishVideoIfNeeded(video) | ||
29 | return video | ||
30 | } | ||
31 | |||
32 | // --------------------------------------------------------------------------- | ||
33 | |||
34 | export { | ||
35 | processVideoFileImport | ||
36 | } | ||
37 | |||
38 | // --------------------------------------------------------------------------- | ||
39 | |||
40 | async function updateVideoFile (video: VideoModel, inputFilePath: string) { | ||
41 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) | ||
42 | const { size } = await stat(inputFilePath) | ||
43 | const fps = await getVideoFileFPS(inputFilePath) | ||
44 | |||
45 | let updatedVideoFile = new VideoFileModel({ | ||
46 | resolution: videoFileResolution, | ||
47 | extname: extname(inputFilePath), | ||
48 | size, | ||
49 | fps, | ||
50 | videoId: video.id | ||
51 | }) | ||
52 | |||
53 | const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) | ||
54 | |||
55 | if (currentVideoFile) { | ||
56 | // Remove old file and old torrent | ||
57 | await video.removeFile(currentVideoFile) | ||
58 | await video.removeTorrent(currentVideoFile) | ||
59 | // Remove the old video file from the array | ||
60 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) | ||
61 | |||
62 | // Update the database | ||
63 | currentVideoFile.set('extname', updatedVideoFile.extname) | ||
64 | currentVideoFile.set('size', updatedVideoFile.size) | ||
65 | currentVideoFile.set('fps', updatedVideoFile.fps) | ||
66 | |||
67 | updatedVideoFile = currentVideoFile | ||
68 | } | ||
69 | |||
70 | const outputPath = video.getVideoFilePath(updatedVideoFile) | ||
71 | await copy(inputFilePath, outputPath) | ||
72 | |||
73 | await video.createTorrentAndSetInfoHash(updatedVideoFile) | ||
74 | |||
75 | await updatedVideoFile.save() | ||
76 | |||
77 | video.VideoFiles.push(updatedVideoFile) | ||
78 | } | ||
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 12004dcd7..1650916a6 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -6,16 +6,20 @@ import { VideoImportState } from '../../../../shared/models/videos' | |||
6 | import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 6 | import { getDurationFromVideoFile, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
7 | import { extname, join } from 'path' | 7 | import { extname, join } from 'path' |
8 | import { VideoFileModel } from '../../../models/video/video-file' | 8 | import { VideoFileModel } from '../../../models/video/video-file' |
9 | import { CONFIG, PREVIEWS_SIZE, sequelizeTypescript, THUMBNAILS_SIZE, VIDEO_IMPORT_TIMEOUT } from '../../../initializers' | 9 | import { VIDEO_IMPORT_TIMEOUT } from '../../../initializers/constants' |
10 | import { downloadImage } from '../../../helpers/requests' | ||
11 | import { VideoState } from '../../../../shared' | 10 | import { VideoState } from '../../../../shared' |
12 | import { JobQueue } from '../index' | 11 | import { JobQueue } from '../index' |
13 | import { federateVideoIfNeeded } from '../../activitypub' | 12 | import { federateVideoIfNeeded } from '../../activitypub' |
14 | import { VideoModel } from '../../../models/video/video' | 13 | import { VideoModel } from '../../../models/video/video' |
15 | import { downloadWebTorrentVideo } from '../../../helpers/webtorrent' | 14 | import { downloadWebTorrentVideo } from '../../../helpers/webtorrent' |
16 | import { getSecureTorrentName } from '../../../helpers/utils' | 15 | import { getSecureTorrentName } from '../../../helpers/utils' |
17 | import { remove, move, stat } from 'fs-extra' | 16 | import { move, remove, stat } from 'fs-extra' |
18 | import { Notifier } from '../../notifier' | 17 | import { Notifier } from '../../notifier' |
18 | import { CONFIG } from '../../../initializers/config' | ||
19 | import { sequelizeTypescript } from '../../../initializers/database' | ||
20 | import { ThumbnailModel } from '../../../models/video/thumbnail' | ||
21 | import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' | ||
22 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | ||
19 | 23 | ||
20 | type VideoImportYoutubeDLPayload = { | 24 | type VideoImportYoutubeDLPayload = { |
21 | type: 'youtube-dl' | 25 | type: 'youtube-dl' |
@@ -144,25 +148,19 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
144 | tempVideoPath = null // This path is not used anymore | 148 | tempVideoPath = null // This path is not used anymore |
145 | 149 | ||
146 | // Process thumbnail | 150 | // Process thumbnail |
147 | if (options.downloadThumbnail) { | 151 | let thumbnailModel: ThumbnailModel |
148 | if (options.thumbnailUrl) { | 152 | if (options.downloadThumbnail && options.thumbnailUrl) { |
149 | await downloadImage(options.thumbnailUrl, CONFIG.STORAGE.THUMBNAILS_DIR, videoImport.Video.getThumbnailName(), THUMBNAILS_SIZE) | 153 | thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE) |
150 | } else { | 154 | } else if (options.generateThumbnail || options.downloadThumbnail) { |
151 | await videoImport.Video.createThumbnail(videoFile) | 155 | thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE) |
152 | } | ||
153 | } else if (options.generateThumbnail) { | ||
154 | await videoImport.Video.createThumbnail(videoFile) | ||
155 | } | 156 | } |
156 | 157 | ||
157 | // Process preview | 158 | // Process preview |
158 | if (options.downloadPreview) { | 159 | let previewModel: ThumbnailModel |
159 | if (options.thumbnailUrl) { | 160 | if (options.downloadPreview && options.thumbnailUrl) { |
160 | await downloadImage(options.thumbnailUrl, CONFIG.STORAGE.PREVIEWS_DIR, videoImport.Video.getPreviewName(), PREVIEWS_SIZE) | 161 | previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW) |
161 | } else { | 162 | } else if (options.generatePreview || options.downloadPreview) { |
162 | await videoImport.Video.createPreview(videoFile) | 163 | previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW) |
163 | } | ||
164 | } else if (options.generatePreview) { | ||
165 | await videoImport.Video.createPreview(videoFile) | ||
166 | } | 164 | } |
167 | 165 | ||
168 | // Create torrent | 166 | // Create torrent |
@@ -182,6 +180,9 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
182 | video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED | 180 | video.state = CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED |
183 | await video.save({ transaction: t }) | 181 | await video.save({ transaction: t }) |
184 | 182 | ||
183 | if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t) | ||
184 | if (previewModel) await video.addAndSaveThumbnail(previewModel, t) | ||
185 | |||
185 | // Now we can federate the video (reload from database, we need more attributes) | 186 | // Now we can federate the video (reload from database, we need more attributes) |
186 | const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) | 187 | const videoForFederation = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) |
187 | await federateVideoIfNeeded(videoForFederation, true, t) | 188 | await federateVideoIfNeeded(videoForFederation, true, t) |
@@ -196,9 +197,14 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
196 | return videoImportUpdated | 197 | return videoImportUpdated |
197 | }) | 198 | }) |
198 | 199 | ||
199 | Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video) | ||
200 | Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) | 200 | Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) |
201 | 201 | ||
202 | if (videoImportUpdated.Video.VideoBlacklist) { | ||
203 | Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video) | ||
204 | } else { | ||
205 | Notifier.Instance.notifyOnNewVideo(videoImportUpdated.Video) | ||
206 | } | ||
207 | |||
202 | // Create transcoding jobs? | 208 | // Create transcoding jobs? |
203 | if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { | 209 | if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { |
204 | // Put uuid because we don't have id auto incremented for now | 210 | // Put uuid because we don't have id auto incremented for now |
@@ -207,7 +213,7 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
207 | isNewVideo: true | 213 | isNewVideo: true |
208 | } | 214 | } |
209 | 215 | ||
210 | await JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput }) | 216 | await JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) |
211 | } | 217 | } |
212 | 218 | ||
213 | } catch (err) { | 219 | } catch (err) { |
diff --git a/server/lib/job-queue/handlers/video-file.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 593e43cc5..48cac517e 100644 --- a/server/lib/job-queue/handlers/video-file.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -8,40 +8,20 @@ import { retryTransactionWrapper } from '../../../helpers/database-utils' | |||
8 | import { sequelizeTypescript } from '../../../initializers' | 8 | import { sequelizeTypescript } from '../../../initializers' |
9 | import * as Bluebird from 'bluebird' | 9 | import * as Bluebird from 'bluebird' |
10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' | 10 | import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' |
11 | import { importVideoFile, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding' | 11 | import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile } from '../../video-transcoding' |
12 | import { Notifier } from '../../notifier' | 12 | import { Notifier } from '../../notifier' |
13 | import { CONFIG } from '../../../initializers/config' | ||
13 | 14 | ||
14 | export type VideoFilePayload = { | 15 | export type VideoTranscodingPayload = { |
15 | videoUUID: string | 16 | videoUUID: string |
16 | isNewVideo?: boolean | ||
17 | resolution?: VideoResolution | 17 | resolution?: VideoResolution |
18 | isNewVideo?: boolean | ||
18 | isPortraitMode?: boolean | 19 | isPortraitMode?: boolean |
20 | generateHlsPlaylist?: boolean | ||
19 | } | 21 | } |
20 | 22 | ||
21 | export type VideoFileImportPayload = { | 23 | async function processVideoTranscoding (job: Bull.Job) { |
22 | videoUUID: string, | 24 | const payload = job.data as VideoTranscodingPayload |
23 | filePath: string | ||
24 | } | ||
25 | |||
26 | async function processVideoFileImport (job: Bull.Job) { | ||
27 | const payload = job.data as VideoFileImportPayload | ||
28 | logger.info('Processing video file import in job %d.', job.id) | ||
29 | |||
30 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID) | ||
31 | // No video, maybe deleted? | ||
32 | if (!video) { | ||
33 | logger.info('Do not process job %d, video does not exist.', job.id) | ||
34 | return undefined | ||
35 | } | ||
36 | |||
37 | await importVideoFile(video, payload.filePath) | ||
38 | |||
39 | await onVideoFileTranscoderOrImportSuccess(video) | ||
40 | return video | ||
41 | } | ||
42 | |||
43 | async function processVideoFile (job: Bull.Job) { | ||
44 | const payload = job.data as VideoFilePayload | ||
45 | logger.info('Processing video file in job %d.', job.id) | 25 | logger.info('Processing video file in job %d.', job.id) |
46 | 26 | ||
47 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID) | 27 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoUUID) |
@@ -51,23 +31,38 @@ async function processVideoFile (job: Bull.Job) { | |||
51 | return undefined | 31 | return undefined |
52 | } | 32 | } |
53 | 33 | ||
54 | // Transcoding in other resolution | 34 | if (payload.generateHlsPlaylist) { |
55 | if (payload.resolution) { | 35 | await generateHlsPlaylist(video, payload.resolution, payload.isPortraitMode || false) |
36 | |||
37 | await retryTransactionWrapper(onHlsPlaylistGenerationSuccess, video) | ||
38 | } else if (payload.resolution) { // Transcoding in other resolution | ||
56 | await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false) | 39 | await transcodeOriginalVideofile(video, payload.resolution, payload.isPortraitMode || false) |
57 | 40 | ||
58 | await retryTransactionWrapper(onVideoFileTranscoderOrImportSuccess, video) | 41 | await retryTransactionWrapper(publishVideoIfNeeded, video, payload) |
59 | } else { | 42 | } else { |
60 | await optimizeVideofile(video) | 43 | await optimizeVideofile(video) |
61 | 44 | ||
62 | await retryTransactionWrapper(onVideoFileOptimizerSuccess, video, payload.isNewVideo) | 45 | await retryTransactionWrapper(onVideoFileOptimizerSuccess, video, payload) |
63 | } | 46 | } |
64 | 47 | ||
65 | return video | 48 | return video |
66 | } | 49 | } |
67 | 50 | ||
68 | async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { | 51 | async function onHlsPlaylistGenerationSuccess (video: VideoModel) { |
69 | if (video === undefined) return undefined | 52 | if (video === undefined) return undefined |
70 | 53 | ||
54 | await sequelizeTypescript.transaction(async t => { | ||
55 | // Maybe the video changed in database, refresh it | ||
56 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) | ||
57 | // Video does not exist anymore | ||
58 | if (!videoDatabase) return undefined | ||
59 | |||
60 | // If the video was not published, we consider it is a new one for other instances | ||
61 | await federateVideoIfNeeded(videoDatabase, false, t) | ||
62 | }) | ||
63 | } | ||
64 | |||
65 | async function publishVideoIfNeeded (video: VideoModel, payload?: VideoTranscodingPayload) { | ||
71 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { | 66 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { |
72 | // Maybe the video changed in database, refresh it | 67 | // Maybe the video changed in database, refresh it |
73 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) | 68 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) |
@@ -93,11 +88,13 @@ async function onVideoFileTranscoderOrImportSuccess (video: VideoModel) { | |||
93 | 88 | ||
94 | if (videoPublished) { | 89 | if (videoPublished) { |
95 | Notifier.Instance.notifyOnNewVideo(videoDatabase) | 90 | Notifier.Instance.notifyOnNewVideo(videoDatabase) |
96 | Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase) | 91 | Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) |
97 | } | 92 | } |
93 | |||
94 | await createHlsJobIfEnabled(payload) | ||
98 | } | 95 | } |
99 | 96 | ||
100 | async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: boolean) { | 97 | async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: VideoTranscodingPayload) { |
101 | if (videoArg === undefined) return undefined | 98 | if (videoArg === undefined) return undefined |
102 | 99 | ||
103 | // Outside the transaction (IO on disk) | 100 | // Outside the transaction (IO on disk) |
@@ -119,7 +116,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo | |||
119 | let videoPublished = false | 116 | let videoPublished = false |
120 | 117 | ||
121 | if (resolutionsEnabled.length !== 0) { | 118 | if (resolutionsEnabled.length !== 0) { |
122 | const tasks: Bluebird<Bull.Job<any>>[] = [] | 119 | const tasks: (Bluebird<Bull.Job<any>> | Promise<Bull.Job<any>>)[] = [] |
123 | 120 | ||
124 | for (const resolution of resolutionsEnabled) { | 121 | for (const resolution of resolutionsEnabled) { |
125 | const dataInput = { | 122 | const dataInput = { |
@@ -127,7 +124,7 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo | |||
127 | resolution | 124 | resolution |
128 | } | 125 | } |
129 | 126 | ||
130 | const p = JobQueue.Instance.createJob({ type: 'video-file', payload: dataInput }) | 127 | const p = JobQueue.Instance.createJob({ type: 'video-transcoding', payload: dataInput }) |
131 | tasks.push(p) | 128 | tasks.push(p) |
132 | } | 129 | } |
133 | 130 | ||
@@ -144,18 +141,37 @@ async function onVideoFileOptimizerSuccess (videoArg: VideoModel, isNewVideo: bo | |||
144 | logger.info('No transcoding jobs created for video %s (no resolutions).', videoDatabase.uuid, { privacy: videoDatabase.privacy }) | 141 | logger.info('No transcoding jobs created for video %s (no resolutions).', videoDatabase.uuid, { privacy: videoDatabase.privacy }) |
145 | } | 142 | } |
146 | 143 | ||
147 | await federateVideoIfNeeded(videoDatabase, isNewVideo, t) | 144 | await federateVideoIfNeeded(videoDatabase, payload.isNewVideo, t) |
148 | 145 | ||
149 | return { videoDatabase, videoPublished } | 146 | return { videoDatabase, videoPublished } |
150 | }) | 147 | }) |
151 | 148 | ||
152 | if (isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) | 149 | if (payload.isNewVideo) Notifier.Instance.notifyOnNewVideo(videoDatabase) |
153 | if (videoPublished) Notifier.Instance.notifyOnPendingVideoPublished(videoDatabase) | 150 | if (videoPublished) Notifier.Instance.notifyOnVideoPublishedAfterTranscoding(videoDatabase) |
151 | |||
152 | await createHlsJobIfEnabled(Object.assign({}, payload, { resolution: videoDatabase.getOriginalFile().resolution })) | ||
154 | } | 153 | } |
155 | 154 | ||
156 | // --------------------------------------------------------------------------- | 155 | // --------------------------------------------------------------------------- |
157 | 156 | ||
158 | export { | 157 | export { |
159 | processVideoFile, | 158 | processVideoTranscoding, |
160 | processVideoFileImport | 159 | publishVideoIfNeeded |
160 | } | ||
161 | |||
162 | // --------------------------------------------------------------------------- | ||
163 | |||
164 | function createHlsJobIfEnabled (payload?: VideoTranscodingPayload) { | ||
165 | // Generate HLS playlist? | ||
166 | if (payload && CONFIG.TRANSCODING.HLS.ENABLED) { | ||
167 | const hlsTranscodingPayload = { | ||
168 | videoUUID: payload.videoUUID, | ||
169 | resolution: payload.resolution, | ||
170 | isPortraitMode: payload.isPortraitMode, | ||
171 | |||
172 | generateHlsPlaylist: true | ||
173 | } | ||
174 | |||
175 | return JobQueue.Instance.createJob({ type: 'video-transcoding', payload: hlsTranscodingPayload }) | ||
176 | } | ||
161 | } | 177 | } |
diff --git a/server/lib/job-queue/job-queue.ts b/server/lib/job-queue/job-queue.ts index ba9cbe0d9..3c810da98 100644 --- a/server/lib/job-queue/job-queue.ts +++ b/server/lib/job-queue/job-queue.ts | |||
@@ -2,16 +2,17 @@ import * as Bull from 'bull' | |||
2 | import { JobState, JobType } from '../../../shared/models' | 2 | import { JobState, JobType } from '../../../shared/models' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { Redis } from '../redis' | 4 | import { Redis } from '../redis' |
5 | import { CONFIG, JOB_ATTEMPTS, JOB_COMPLETED_LIFETIME, JOB_CONCURRENCY, JOB_TTL, REPEAT_JOBS } from '../../initializers' | 5 | import { JOB_ATTEMPTS, JOB_COMPLETED_LIFETIME, JOB_CONCURRENCY, JOB_TTL, REPEAT_JOBS, WEBSERVER } from '../../initializers/constants' |
6 | import { ActivitypubHttpBroadcastPayload, processActivityPubHttpBroadcast } from './handlers/activitypub-http-broadcast' | 6 | import { ActivitypubHttpBroadcastPayload, processActivityPubHttpBroadcast } from './handlers/activitypub-http-broadcast' |
7 | import { ActivitypubHttpFetcherPayload, processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' | 7 | import { ActivitypubHttpFetcherPayload, processActivityPubHttpFetcher } from './handlers/activitypub-http-fetcher' |
8 | import { ActivitypubHttpUnicastPayload, processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' | 8 | import { ActivitypubHttpUnicastPayload, processActivityPubHttpUnicast } from './handlers/activitypub-http-unicast' |
9 | import { EmailPayload, processEmail } from './handlers/email' | 9 | import { EmailPayload, processEmail } from './handlers/email' |
10 | import { processVideoFile, processVideoFileImport, VideoFileImportPayload, VideoFilePayload } from './handlers/video-file' | 10 | import { processVideoTranscoding, VideoTranscodingPayload } from './handlers/video-transcoding' |
11 | import { ActivitypubFollowPayload, processActivityPubFollow } from './handlers/activitypub-follow' | 11 | import { ActivitypubFollowPayload, processActivityPubFollow } from './handlers/activitypub-follow' |
12 | import { processVideoImport, VideoImportPayload } from './handlers/video-import' | 12 | import { processVideoImport, VideoImportPayload } from './handlers/video-import' |
13 | import { processVideosViews } from './handlers/video-views' | 13 | import { processVideosViews } from './handlers/video-views' |
14 | import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher' | 14 | import { refreshAPObject, RefreshPayload } from './handlers/activitypub-refresher' |
15 | import { processVideoFileImport, VideoFileImportPayload } from './handlers/video-file-import' | ||
15 | 16 | ||
16 | type CreateJobArgument = | 17 | type CreateJobArgument = |
17 | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | | 18 | { type: 'activitypub-http-broadcast', payload: ActivitypubHttpBroadcastPayload } | |
@@ -19,19 +20,20 @@ type CreateJobArgument = | |||
19 | { type: 'activitypub-http-fetcher', payload: ActivitypubHttpFetcherPayload } | | 20 | { type: 'activitypub-http-fetcher', payload: ActivitypubHttpFetcherPayload } | |
20 | { type: 'activitypub-follow', payload: ActivitypubFollowPayload } | | 21 | { type: 'activitypub-follow', payload: ActivitypubFollowPayload } | |
21 | { type: 'video-file-import', payload: VideoFileImportPayload } | | 22 | { type: 'video-file-import', payload: VideoFileImportPayload } | |
22 | { type: 'video-file', payload: VideoFilePayload } | | 23 | { type: 'video-transcoding', payload: VideoTranscodingPayload } | |
23 | { type: 'email', payload: EmailPayload } | | 24 | { type: 'email', payload: EmailPayload } | |
24 | { type: 'video-import', payload: VideoImportPayload } | | 25 | { type: 'video-import', payload: VideoImportPayload } | |
25 | { type: 'activitypub-refresher', payload: RefreshPayload } | | 26 | { type: 'activitypub-refresher', payload: RefreshPayload } | |
26 | { type: 'videos-views', payload: {} } | 27 | { type: 'videos-views', payload: {} } |
27 | 28 | ||
28 | const handlers: { [ id in JobType ]: (job: Bull.Job) => Promise<any>} = { | 29 | const handlers: { [ id in (JobType | 'video-file') ]: (job: Bull.Job) => Promise<any>} = { |
29 | 'activitypub-http-broadcast': processActivityPubHttpBroadcast, | 30 | 'activitypub-http-broadcast': processActivityPubHttpBroadcast, |
30 | 'activitypub-http-unicast': processActivityPubHttpUnicast, | 31 | 'activitypub-http-unicast': processActivityPubHttpUnicast, |
31 | 'activitypub-http-fetcher': processActivityPubHttpFetcher, | 32 | 'activitypub-http-fetcher': processActivityPubHttpFetcher, |
32 | 'activitypub-follow': processActivityPubFollow, | 33 | 'activitypub-follow': processActivityPubFollow, |
33 | 'video-file-import': processVideoFileImport, | 34 | 'video-file-import': processVideoFileImport, |
34 | 'video-file': processVideoFile, | 35 | 'video-transcoding': processVideoTranscoding, |
36 | 'video-file': processVideoTranscoding, // TODO: remove it (changed in 1.3) | ||
35 | 'email': processEmail, | 37 | 'email': processEmail, |
36 | 'video-import': processVideoImport, | 38 | 'video-import': processVideoImport, |
37 | 'videos-views': processVideosViews, | 39 | 'videos-views': processVideosViews, |
@@ -44,7 +46,7 @@ const jobTypes: JobType[] = [ | |||
44 | 'activitypub-http-fetcher', | 46 | 'activitypub-http-fetcher', |
45 | 'activitypub-http-unicast', | 47 | 'activitypub-http-unicast', |
46 | 'email', | 48 | 'email', |
47 | 'video-file', | 49 | 'video-transcoding', |
48 | 'video-file-import', | 50 | 'video-file-import', |
49 | 'video-import', | 51 | 'video-import', |
50 | 'videos-views', | 52 | 'videos-views', |
@@ -66,10 +68,10 @@ class JobQueue { | |||
66 | if (this.initialized === true) return | 68 | if (this.initialized === true) return |
67 | this.initialized = true | 69 | this.initialized = true |
68 | 70 | ||
69 | this.jobRedisPrefix = 'bull-' + CONFIG.WEBSERVER.HOST | 71 | this.jobRedisPrefix = 'bull-' + WEBSERVER.HOST |
70 | const queueOptions = { | 72 | const queueOptions = { |
71 | prefix: this.jobRedisPrefix, | 73 | prefix: this.jobRedisPrefix, |
72 | redis: Redis.getRedisClient(), | 74 | redis: Redis.getRedisClientOptions(), |
73 | settings: { | 75 | settings: { |
74 | maxStalledCount: 10 // transcoding could be long, so jobs can often be interrupted by restarts | 76 | maxStalledCount: 10 // transcoding could be long, so jobs can often be interrupted by restarts |
75 | } | 77 | } |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index 2fa320cd7..c1e63fa8f 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -6,7 +6,7 @@ import { UserNotificationModel } from '../models/account/user-notification' | |||
6 | import { VideoCommentModel } from '../models/video/video-comment' | 6 | import { VideoCommentModel } from '../models/video/video-comment' |
7 | import { UserModel } from '../models/account/user' | 7 | import { UserModel } from '../models/account/user' |
8 | import { PeerTubeSocket } from './peertube-socket' | 8 | import { PeerTubeSocket } from './peertube-socket' |
9 | import { CONFIG } from '../initializers/constants' | 9 | import { CONFIG } from '../initializers/config' |
10 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' | 10 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' |
11 | import { VideoAbuseModel } from '../models/video/video-abuse' | 11 | import { VideoAbuseModel } from '../models/video/video-abuse' |
12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
@@ -23,19 +23,35 @@ class Notifier { | |||
23 | private constructor () {} | 23 | private constructor () {} |
24 | 24 | ||
25 | notifyOnNewVideo (video: VideoModel): void { | 25 | notifyOnNewVideo (video: VideoModel): void { |
26 | // Only notify on public and published videos | 26 | // Only notify on public and published videos which are not blacklisted |
27 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED) return | 27 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.VideoBlacklist) return |
28 | 28 | ||
29 | this.notifySubscribersOfNewVideo(video) | 29 | this.notifySubscribersOfNewVideo(video) |
30 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) | 30 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) |
31 | } | 31 | } |
32 | 32 | ||
33 | notifyOnPendingVideoPublished (video: VideoModel): void { | 33 | notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void { |
34 | // Only notify on public videos that has been published while the user waited transcoding/scheduled update | 34 | // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update |
35 | if (video.waitTranscoding === false && !video.ScheduleVideoUpdate) return | 35 | if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return |
36 | 36 | ||
37 | this.notifyOwnedVideoHasBeenPublished(video) | 37 | this.notifyOwnedVideoHasBeenPublished(video) |
38 | .catch(err => logger.error('Cannot notify owner that its video %s has been published.', video.url, { err })) | 38 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) |
39 | } | ||
40 | |||
41 | notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void { | ||
42 | // don't notify if video is still blacklisted or waiting for transcoding | ||
43 | if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | ||
44 | |||
45 | this.notifyOwnedVideoHasBeenPublished(video) | ||
46 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) | ||
47 | } | ||
48 | |||
49 | notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void { | ||
50 | // don't notify if video is still waiting for transcoding or scheduled update | ||
51 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | ||
52 | |||
53 | this.notifyOwnedVideoHasBeenPublished(video) | ||
54 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length | ||
39 | } | 55 | } |
40 | 56 | ||
41 | notifyOnNewComment (comment: VideoCommentModel): void { | 57 | notifyOnNewComment (comment: VideoCommentModel): void { |
@@ -51,6 +67,11 @@ class Notifier { | |||
51 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) | 67 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) |
52 | } | 68 | } |
53 | 69 | ||
70 | notifyOnVideoAutoBlacklist (video: VideoModel): void { | ||
71 | this.notifyModeratorsOfVideoAutoBlacklist(video) | ||
72 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err })) | ||
73 | } | ||
74 | |||
54 | notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { | 75 | notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { |
55 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) | 76 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) |
56 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) | 77 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) |
@@ -58,7 +79,7 @@ class Notifier { | |||
58 | 79 | ||
59 | notifyOnVideoUnblacklist (video: VideoModel): void { | 80 | notifyOnVideoUnblacklist (video: VideoModel): void { |
60 | this.notifyVideoOwnerOfUnblacklist(video) | 81 | this.notifyVideoOwnerOfUnblacklist(video) |
61 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', video.url, { err })) | 82 | .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) |
62 | } | 83 | } |
63 | 84 | ||
64 | notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { | 85 | notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { |
@@ -71,18 +92,25 @@ class Notifier { | |||
71 | .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) | 92 | .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) |
72 | } | 93 | } |
73 | 94 | ||
74 | notifyOfNewFollow (actorFollow: ActorFollowModel): void { | 95 | notifyOfNewUserFollow (actorFollow: ActorFollowModel): void { |
75 | this.notifyUserOfNewActorFollow(actorFollow) | 96 | this.notifyUserOfNewActorFollow(actorFollow) |
76 | .catch(err => { | 97 | .catch(err => { |
77 | logger.error( | 98 | logger.error( |
78 | 'Cannot notify owner of channel %s of a new follow by %s.', | 99 | 'Cannot notify owner of channel %s of a new follow by %s.', |
79 | actorFollow.ActorFollowing.VideoChannel.getDisplayName(), | 100 | actorFollow.ActorFollowing.VideoChannel.getDisplayName(), |
80 | actorFollow.ActorFollower.Account.getDisplayName(), | 101 | actorFollow.ActorFollower.Account.getDisplayName(), |
81 | err | 102 | { err } |
82 | ) | 103 | ) |
83 | }) | 104 | }) |
84 | } | 105 | } |
85 | 106 | ||
107 | notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void { | ||
108 | this.notifyAdminsOfNewInstanceFollow(actorFollow) | ||
109 | .catch(err => { | ||
110 | logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }) | ||
111 | }) | ||
112 | } | ||
113 | |||
86 | private async notifySubscribersOfNewVideo (video: VideoModel) { | 114 | private async notifySubscribersOfNewVideo (video: VideoModel) { |
87 | // List all followers that are users | 115 | // List all followers that are users |
88 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) | 116 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) |
@@ -147,10 +175,13 @@ class Notifier { | |||
147 | } | 175 | } |
148 | 176 | ||
149 | private async notifyOfCommentMention (comment: VideoCommentModel) { | 177 | private async notifyOfCommentMention (comment: VideoCommentModel) { |
150 | const usernames = comment.extractMentions() | 178 | const extractedUsernames = comment.extractMentions() |
151 | logger.debug('Extracted %d username from comment %s.', usernames.length, comment.url, { usernames, text: comment.text }) | 179 | logger.debug( |
180 | 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url, | ||
181 | { usernames: extractedUsernames, text: comment.text } | ||
182 | ) | ||
152 | 183 | ||
153 | let users = await UserModel.listByUsernames(usernames) | 184 | let users = await UserModel.listByUsernames(extractedUsernames) |
154 | 185 | ||
155 | if (comment.Video.isOwned()) { | 186 | if (comment.Video.isOwned()) { |
156 | const userException = await UserModel.loadByVideoId(comment.videoId) | 187 | const userException = await UserModel.loadByVideoId(comment.videoId) |
@@ -237,6 +268,33 @@ class Notifier { | |||
237 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 268 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
238 | } | 269 | } |
239 | 270 | ||
271 | private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) { | ||
272 | const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) | ||
273 | |||
274 | logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) | ||
275 | |||
276 | function settingGetter (user: UserModel) { | ||
277 | return user.NotificationSetting.newInstanceFollower | ||
278 | } | ||
279 | |||
280 | async function notificationCreator (user: UserModel) { | ||
281 | const notification = await UserNotificationModel.create({ | ||
282 | type: UserNotificationType.NEW_INSTANCE_FOLLOWER, | ||
283 | userId: user.id, | ||
284 | actorFollowId: actorFollow.id | ||
285 | }) | ||
286 | notification.ActorFollow = actorFollow | ||
287 | |||
288 | return notification | ||
289 | } | ||
290 | |||
291 | function emailSender (emails: string[]) { | ||
292 | return Emailer.Instance.addNewInstanceFollowerNotification(emails, actorFollow) | ||
293 | } | ||
294 | |||
295 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) | ||
296 | } | ||
297 | |||
240 | private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { | 298 | private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { |
241 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) | 299 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) |
242 | if (moderators.length === 0) return | 300 | if (moderators.length === 0) return |
@@ -265,6 +323,34 @@ class Notifier { | |||
265 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 323 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
266 | } | 324 | } |
267 | 325 | ||
326 | private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) { | ||
327 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) | ||
328 | if (moderators.length === 0) return | ||
329 | |||
330 | logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url) | ||
331 | |||
332 | function settingGetter (user: UserModel) { | ||
333 | return user.NotificationSetting.videoAutoBlacklistAsModerator | ||
334 | } | ||
335 | async function notificationCreator (user: UserModel) { | ||
336 | |||
337 | const notification = await UserNotificationModel.create({ | ||
338 | type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS, | ||
339 | userId: user.id, | ||
340 | videoId: video.id | ||
341 | }) | ||
342 | notification.Video = video | ||
343 | |||
344 | return notification | ||
345 | } | ||
346 | |||
347 | function emailSender (emails: string[]) { | ||
348 | return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video) | ||
349 | } | ||
350 | |||
351 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | ||
352 | } | ||
353 | |||
268 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { | 354 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { |
269 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) | 355 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) |
270 | if (!user) return | 356 | if (!user) return |
@@ -436,7 +522,7 @@ class Notifier { | |||
436 | } | 522 | } |
437 | 523 | ||
438 | private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) { | 524 | private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) { |
439 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified !== true) return false | 525 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false |
440 | 526 | ||
441 | return value & UserNotificationSettingValue.EMAIL | 527 | return value & UserNotificationSettingValue.EMAIL |
442 | } | 528 | } |
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index 2cd2ae97c..45ac3e7c4 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -4,12 +4,13 @@ import { logger } from '../helpers/logger' | |||
4 | import { UserModel } from '../models/account/user' | 4 | import { UserModel } from '../models/account/user' |
5 | import { OAuthClientModel } from '../models/oauth/oauth-client' | 5 | import { OAuthClientModel } from '../models/oauth/oauth-client' |
6 | import { OAuthTokenModel } from '../models/oauth/oauth-token' | 6 | import { OAuthTokenModel } from '../models/oauth/oauth-token' |
7 | import { CONFIG } from '../initializers/constants' | 7 | import { CACHE } from '../initializers/constants' |
8 | import { Transaction } from 'sequelize' | 8 | import { Transaction } from 'sequelize' |
9 | import { CONFIG } from '../initializers/config' | ||
9 | 10 | ||
10 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } | 11 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } |
11 | const accessTokenCache: { [ accessToken: string ]: OAuthTokenModel } = {} | 12 | let accessTokenCache: { [ accessToken: string ]: OAuthTokenModel } = {} |
12 | const userHavingToken: { [ userId: number ]: string } = {} | 13 | let userHavingToken: { [ userId: number ]: string } = {} |
13 | 14 | ||
14 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
15 | 16 | ||
@@ -38,11 +39,19 @@ function clearCacheByToken (token: string) { | |||
38 | function getAccessToken (bearerToken: string) { | 39 | function getAccessToken (bearerToken: string) { |
39 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') | 40 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') |
40 | 41 | ||
42 | if (!bearerToken) return Bluebird.resolve(undefined) | ||
43 | |||
41 | if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken]) | 44 | if (accessTokenCache[bearerToken] !== undefined) return Bluebird.resolve(accessTokenCache[bearerToken]) |
42 | 45 | ||
43 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) | 46 | return OAuthTokenModel.getByTokenAndPopulateUser(bearerToken) |
44 | .then(tokenModel => { | 47 | .then(tokenModel => { |
45 | if (tokenModel) { | 48 | if (tokenModel) { |
49 | // Reinit our cache | ||
50 | if (Object.keys(accessTokenCache).length > CACHE.USER_TOKENS.MAX_SIZE) { | ||
51 | accessTokenCache = {} | ||
52 | userHavingToken = {} | ||
53 | } | ||
54 | |||
46 | accessTokenCache[ bearerToken ] = tokenModel | 55 | accessTokenCache[ bearerToken ] = tokenModel |
47 | userHavingToken[ tokenModel.userId ] = tokenModel.accessToken | 56 | userHavingToken[ tokenModel.userId ] = tokenModel.accessToken |
48 | } | 57 | } |
diff --git a/server/lib/redis.ts b/server/lib/redis.ts index 3628c0583..f77d0b62c 100644 --- a/server/lib/redis.ts +++ b/server/lib/redis.ts | |||
@@ -3,12 +3,13 @@ import { createClient, RedisClient } from 'redis' | |||
3 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
4 | import { generateRandomString } from '../helpers/utils' | 4 | import { generateRandomString } from '../helpers/utils' |
5 | import { | 5 | import { |
6 | CONFIG, | ||
7 | CONTACT_FORM_LIFETIME, | 6 | CONTACT_FORM_LIFETIME, |
8 | USER_EMAIL_VERIFY_LIFETIME, | 7 | USER_EMAIL_VERIFY_LIFETIME, |
9 | USER_PASSWORD_RESET_LIFETIME, | 8 | USER_PASSWORD_RESET_LIFETIME, |
10 | VIDEO_VIEW_LIFETIME | 9 | VIDEO_VIEW_LIFETIME, |
11 | } from '../initializers' | 10 | WEBSERVER |
11 | } from '../initializers/constants' | ||
12 | import { CONFIG } from '../initializers/config' | ||
12 | 13 | ||
13 | type CachedRoute = { | 14 | type CachedRoute = { |
14 | body: string, | 15 | body: string, |
@@ -30,7 +31,7 @@ class Redis { | |||
30 | if (this.initialized === true) return | 31 | if (this.initialized === true) return |
31 | this.initialized = true | 32 | this.initialized = true |
32 | 33 | ||
33 | this.client = createClient(Redis.getRedisClient()) | 34 | this.client = createClient(Redis.getRedisClientOptions()) |
34 | 35 | ||
35 | this.client.on('error', err => { | 36 | this.client.on('error', err => { |
36 | logger.error('Error in Redis client.', { err }) | 37 | logger.error('Error in Redis client.', { err }) |
@@ -41,10 +42,10 @@ class Redis { | |||
41 | this.client.auth(CONFIG.REDIS.AUTH) | 42 | this.client.auth(CONFIG.REDIS.AUTH) |
42 | } | 43 | } |
43 | 44 | ||
44 | this.prefix = 'redis-' + CONFIG.WEBSERVER.HOST + '-' | 45 | this.prefix = 'redis-' + WEBSERVER.HOST + '-' |
45 | } | 46 | } |
46 | 47 | ||
47 | static getRedisClient () { | 48 | static getRedisClientOptions () { |
48 | return Object.assign({}, | 49 | return Object.assign({}, |
49 | (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, | 50 | (CONFIG.REDIS.AUTH && CONFIG.REDIS.AUTH != null) ? { password: CONFIG.REDIS.AUTH } : {}, |
50 | (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, | 51 | (CONFIG.REDIS.DB) ? { db: CONFIG.REDIS.DB } : {}, |
@@ -54,6 +55,14 @@ class Redis { | |||
54 | ) | 55 | ) |
55 | } | 56 | } |
56 | 57 | ||
58 | getClient () { | ||
59 | return this.client | ||
60 | } | ||
61 | |||
62 | getPrefix () { | ||
63 | return this.prefix | ||
64 | } | ||
65 | |||
57 | /************* Forgot password *************/ | 66 | /************* Forgot password *************/ |
58 | 67 | ||
59 | async setResetPasswordVerificationString (userId: number) { | 68 | async setResetPasswordVerificationString (userId: number) { |
@@ -88,7 +97,7 @@ class Redis { | |||
88 | return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME) | 97 | return this.setValue(this.generateContactFormKey(ip), '1', CONTACT_FORM_LIFETIME) |
89 | } | 98 | } |
90 | 99 | ||
91 | async isContactFormIpExists (ip: string) { | 100 | async doesContactFormIpExist (ip: string) { |
92 | return this.exists(this.generateContactFormKey(ip)) | 101 | return this.exists(this.generateContactFormKey(ip)) |
93 | } | 102 | } |
94 | 103 | ||
@@ -98,7 +107,7 @@ class Redis { | |||
98 | return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) | 107 | return this.setValue(this.generateViewKey(ip, videoUUID), '1', VIDEO_VIEW_LIFETIME) |
99 | } | 108 | } |
100 | 109 | ||
101 | async isVideoIPViewExists (ip: string, videoUUID: string) { | 110 | async doesVideoIPViewExist (ip: string, videoUUID: string) { |
102 | return this.exists(this.generateViewKey(ip, videoUUID)) | 111 | return this.exists(this.generateViewKey(ip, videoUUID)) |
103 | } | 112 | } |
104 | 113 | ||
diff --git a/server/lib/schedulers/abstract-scheduler.ts b/server/lib/schedulers/abstract-scheduler.ts index 86ea7aa38..0e6088911 100644 --- a/server/lib/schedulers/abstract-scheduler.ts +++ b/server/lib/schedulers/abstract-scheduler.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { logger } from '../../helpers/logger' | 1 | import { logger } from '../../helpers/logger' |
2 | import * as Bluebird from 'bluebird' | ||
2 | 3 | ||
3 | export abstract class AbstractScheduler { | 4 | export abstract class AbstractScheduler { |
4 | 5 | ||
@@ -30,5 +31,5 @@ export abstract class AbstractScheduler { | |||
30 | } | 31 | } |
31 | } | 32 | } |
32 | 33 | ||
33 | protected abstract internalExecute (): Promise<any> | 34 | protected abstract internalExecute (): Promise<any> | Bluebird<any> |
34 | } | 35 | } |
diff --git a/server/lib/schedulers/actor-follow-scheduler.ts b/server/lib/schedulers/actor-follow-scheduler.ts index 3967be7f8..fdd3ad5fa 100644 --- a/server/lib/schedulers/actor-follow-scheduler.ts +++ b/server/lib/schedulers/actor-follow-scheduler.ts | |||
@@ -2,8 +2,8 @@ import { isTestInstance } from '../../helpers/core-utils' | |||
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../helpers/logger' |
3 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
4 | import { AbstractScheduler } from './abstract-scheduler' | 4 | import { AbstractScheduler } from './abstract-scheduler' |
5 | import { SCHEDULER_INTERVALS_MS } from '../../initializers' | 5 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
6 | import { ActorFollowScoreCache } from '../cache' | 6 | import { ActorFollowScoreCache } from '../files-cache' |
7 | 7 | ||
8 | export class ActorFollowScheduler extends AbstractScheduler { | 8 | export class ActorFollowScheduler extends AbstractScheduler { |
9 | 9 | ||
diff --git a/server/lib/schedulers/remove-old-history-scheduler.ts b/server/lib/schedulers/remove-old-history-scheduler.ts new file mode 100644 index 000000000..1b5ff8394 --- /dev/null +++ b/server/lib/schedulers/remove-old-history-scheduler.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | import { logger } from '../../helpers/logger' | ||
2 | import { AbstractScheduler } from './abstract-scheduler' | ||
3 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | ||
4 | import { UserVideoHistoryModel } from '../../models/account/user-video-history' | ||
5 | import { CONFIG } from '../../initializers/config' | ||
6 | import { isTestInstance } from '../../helpers/core-utils' | ||
7 | |||
8 | export class RemoveOldHistoryScheduler extends AbstractScheduler { | ||
9 | |||
10 | private static instance: AbstractScheduler | ||
11 | |||
12 | protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.removeOldHistory | ||
13 | |||
14 | private constructor () { | ||
15 | super() | ||
16 | } | ||
17 | |||
18 | protected internalExecute () { | ||
19 | if (CONFIG.HISTORY.VIDEOS.MAX_AGE === -1) return | ||
20 | |||
21 | logger.info('Removing old videos history.') | ||
22 | |||
23 | const now = new Date() | ||
24 | const beforeDate = new Date(now.getTime() - CONFIG.HISTORY.VIDEOS.MAX_AGE).toISOString() | ||
25 | |||
26 | return UserVideoHistoryModel.removeOldHistory(beforeDate) | ||
27 | } | ||
28 | |||
29 | static get Instance () { | ||
30 | return this.instance || (this.instance = new this()) | ||
31 | } | ||
32 | } | ||
diff --git a/server/lib/schedulers/remove-old-jobs-scheduler.ts b/server/lib/schedulers/remove-old-jobs-scheduler.ts index 4a4341ba9..0179a7618 100644 --- a/server/lib/schedulers/remove-old-jobs-scheduler.ts +++ b/server/lib/schedulers/remove-old-jobs-scheduler.ts | |||
@@ -2,7 +2,7 @@ import { isTestInstance } from '../../helpers/core-utils' | |||
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../helpers/logger' |
3 | import { JobQueue } from '../job-queue' | 3 | import { JobQueue } from '../job-queue' |
4 | import { AbstractScheduler } from './abstract-scheduler' | 4 | import { AbstractScheduler } from './abstract-scheduler' |
5 | import { SCHEDULER_INTERVALS_MS } from '../../initializers' | 5 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
6 | 6 | ||
7 | export class RemoveOldJobsScheduler extends AbstractScheduler { | 7 | export class RemoveOldJobsScheduler extends AbstractScheduler { |
8 | 8 | ||
diff --git a/server/lib/schedulers/remove-old-views-scheduler.ts b/server/lib/schedulers/remove-old-views-scheduler.ts new file mode 100644 index 000000000..39fbb9163 --- /dev/null +++ b/server/lib/schedulers/remove-old-views-scheduler.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import { logger } from '../../helpers/logger' | ||
2 | import { AbstractScheduler } from './abstract-scheduler' | ||
3 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' | ||
4 | import { UserVideoHistoryModel } from '../../models/account/user-video-history' | ||
5 | import { CONFIG } from '../../initializers/config' | ||
6 | import { isTestInstance } from '../../helpers/core-utils' | ||
7 | import { VideoViewModel } from '../../models/video/video-views' | ||
8 | |||
9 | export class RemoveOldViewsScheduler extends AbstractScheduler { | ||
10 | |||
11 | private static instance: AbstractScheduler | ||
12 | |||
13 | protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.removeOldViews | ||
14 | |||
15 | private constructor () { | ||
16 | super() | ||
17 | } | ||
18 | |||
19 | protected internalExecute () { | ||
20 | if (CONFIG.VIEWS.VIDEOS.REMOTE.MAX_AGE === -1) return | ||
21 | |||
22 | logger.info('Removing old videos views.') | ||
23 | |||
24 | const now = new Date() | ||
25 | const beforeDate = new Date(now.getTime() - CONFIG.VIEWS.VIDEOS.REMOTE.MAX_AGE).toISOString() | ||
26 | |||
27 | return VideoViewModel.removeOldRemoteViewsHistory(beforeDate) | ||
28 | } | ||
29 | |||
30 | static get Instance () { | ||
31 | return this.instance || (this.instance = new this()) | ||
32 | } | ||
33 | } | ||
diff --git a/server/lib/schedulers/update-videos-scheduler.ts b/server/lib/schedulers/update-videos-scheduler.ts index 2618a5857..80080a132 100644 --- a/server/lib/schedulers/update-videos-scheduler.ts +++ b/server/lib/schedulers/update-videos-scheduler.ts | |||
@@ -3,10 +3,11 @@ import { AbstractScheduler } from './abstract-scheduler' | |||
3 | import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-update' | 3 | import { ScheduleVideoUpdateModel } from '../../models/video/schedule-video-update' |
4 | import { retryTransactionWrapper } from '../../helpers/database-utils' | 4 | import { retryTransactionWrapper } from '../../helpers/database-utils' |
5 | import { federateVideoIfNeeded } from '../activitypub' | 5 | import { federateVideoIfNeeded } from '../activitypub' |
6 | import { SCHEDULER_INTERVALS_MS, sequelizeTypescript } from '../../initializers' | 6 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
7 | import { VideoPrivacy } from '../../../shared/models/videos' | 7 | import { VideoPrivacy } from '../../../shared/models/videos' |
8 | import { Notifier } from '../notifier' | 8 | import { Notifier } from '../notifier' |
9 | import { VideoModel } from '../../models/video/video' | 9 | import { VideoModel } from '../../models/video/video' |
10 | import { sequelizeTypescript } from '../../initializers/database' | ||
10 | 11 | ||
11 | export class UpdateVideosScheduler extends AbstractScheduler { | 12 | export class UpdateVideosScheduler extends AbstractScheduler { |
12 | 13 | ||
@@ -57,7 +58,7 @@ export class UpdateVideosScheduler extends AbstractScheduler { | |||
57 | 58 | ||
58 | for (const v of publishedVideos) { | 59 | for (const v of publishedVideos) { |
59 | Notifier.Instance.notifyOnNewVideo(v) | 60 | Notifier.Instance.notifyOnNewVideo(v) |
60 | Notifier.Instance.notifyOnPendingVideoPublished(v) | 61 | Notifier.Instance.notifyOnVideoPublishedAfterScheduledUpdate(v) |
61 | } | 62 | } |
62 | } | 63 | } |
63 | 64 | ||
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index f643ee226..01af1e9d2 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { AbstractScheduler } from './abstract-scheduler' | 1 | import { AbstractScheduler } from './abstract-scheduler' |
2 | import { CONFIG, REDUNDANCY, VIDEO_IMPORT_TIMEOUT } from '../../initializers' | 2 | import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } from '../../initializers/constants' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { VideosRedundancy } from '../../../shared/models/redundancy' | 4 | import { VideosRedundancy } from '../../../shared/models/redundancy' |
5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
@@ -9,9 +9,20 @@ import { join } from 'path' | |||
9 | import { move } from 'fs-extra' | 9 | import { move } from 'fs-extra' |
10 | import { getServerActor } from '../../helpers/utils' | 10 | import { getServerActor } from '../../helpers/utils' |
11 | import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' | 11 | import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' |
12 | import { getVideoCacheFileActivityPubUrl } from '../activitypub/url' | 12 | import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' |
13 | import { removeVideoRedundancy } from '../redundancy' | 13 | import { removeVideoRedundancy } from '../redundancy' |
14 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' | 14 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' |
15 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | ||
16 | import { VideoModel } from '../../models/video/video' | ||
17 | import { downloadPlaylistSegments } from '../hls' | ||
18 | import { CONFIG } from '../../initializers/config' | ||
19 | |||
20 | type CandidateToDuplicate = { | ||
21 | redundancy: VideosRedundancy, | ||
22 | video: VideoModel, | ||
23 | files: VideoFileModel[], | ||
24 | streamingPlaylists: VideoStreamingPlaylistModel[] | ||
25 | } | ||
15 | 26 | ||
16 | export class VideosRedundancyScheduler extends AbstractScheduler { | 27 | export class VideosRedundancyScheduler extends AbstractScheduler { |
17 | 28 | ||
@@ -24,28 +35,32 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
24 | } | 35 | } |
25 | 36 | ||
26 | protected async internalExecute () { | 37 | protected async internalExecute () { |
27 | for (const obj of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) { | 38 | for (const redundancyConfig of CONFIG.REDUNDANCY.VIDEOS.STRATEGIES) { |
28 | logger.info('Running redundancy scheduler for strategy %s.', obj.strategy) | 39 | logger.info('Running redundancy scheduler for strategy %s.', redundancyConfig.strategy) |
29 | 40 | ||
30 | try { | 41 | try { |
31 | const videoToDuplicate = await this.findVideoToDuplicate(obj) | 42 | const videoToDuplicate = await this.findVideoToDuplicate(redundancyConfig) |
32 | if (!videoToDuplicate) continue | 43 | if (!videoToDuplicate) continue |
33 | 44 | ||
34 | const videoFiles = videoToDuplicate.VideoFiles | 45 | const candidateToDuplicate = { |
35 | videoFiles.forEach(f => f.Video = videoToDuplicate) | 46 | video: videoToDuplicate, |
47 | redundancy: redundancyConfig, | ||
48 | files: videoToDuplicate.VideoFiles, | ||
49 | streamingPlaylists: videoToDuplicate.VideoStreamingPlaylists | ||
50 | } | ||
36 | 51 | ||
37 | await this.purgeCacheIfNeeded(obj, videoFiles) | 52 | await this.purgeCacheIfNeeded(candidateToDuplicate) |
38 | 53 | ||
39 | if (await this.isTooHeavy(obj, videoFiles)) { | 54 | if (await this.isTooHeavy(candidateToDuplicate)) { |
40 | logger.info('Video %s is too big for our cache, skipping.', videoToDuplicate.url) | 55 | logger.info('Video %s is too big for our cache, skipping.', videoToDuplicate.url) |
41 | continue | 56 | continue |
42 | } | 57 | } |
43 | 58 | ||
44 | logger.info('Will duplicate video %s in redundancy scheduler "%s".', videoToDuplicate.url, obj.strategy) | 59 | logger.info('Will duplicate video %s in redundancy scheduler "%s".', videoToDuplicate.url, redundancyConfig.strategy) |
45 | 60 | ||
46 | await this.createVideoRedundancy(obj, videoFiles) | 61 | await this.createVideoRedundancies(candidateToDuplicate) |
47 | } catch (err) { | 62 | } catch (err) { |
48 | logger.error('Cannot run videos redundancy %s.', obj.strategy, { err }) | 63 | logger.error('Cannot run videos redundancy %s.', redundancyConfig.strategy, { err }) |
49 | } | 64 | } |
50 | } | 65 | } |
51 | 66 | ||
@@ -63,25 +78,35 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
63 | 78 | ||
64 | for (const redundancyModel of expired) { | 79 | for (const redundancyModel of expired) { |
65 | try { | 80 | try { |
66 | await this.extendsOrDeleteRedundancy(redundancyModel) | 81 | const redundancyConfig = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) |
82 | const candidate = { | ||
83 | redundancy: redundancyConfig, | ||
84 | video: null, | ||
85 | files: [], | ||
86 | streamingPlaylists: [] | ||
87 | } | ||
88 | |||
89 | // If the administrator disabled the redundancy or decreased the cache size, remove this redundancy instead of extending it | ||
90 | if (!redundancyConfig || await this.isTooHeavy(candidate)) { | ||
91 | logger.info('Destroying redundancy %s because the cache size %s is too heavy.', redundancyModel.url, redundancyModel.strategy) | ||
92 | await removeVideoRedundancy(redundancyModel) | ||
93 | } else { | ||
94 | await this.extendsRedundancy(redundancyModel) | ||
95 | } | ||
67 | } catch (err) { | 96 | } catch (err) { |
68 | logger.error('Cannot extend expiration of %s video from our redundancy system.', this.buildEntryLogId(redundancyModel)) | 97 | logger.error( |
98 | 'Cannot extend or remove expiration of %s video from our redundancy system.', this.buildEntryLogId(redundancyModel), | ||
99 | { err } | ||
100 | ) | ||
69 | } | 101 | } |
70 | } | 102 | } |
71 | } | 103 | } |
72 | 104 | ||
73 | private async extendsOrDeleteRedundancy (redundancyModel: VideoRedundancyModel) { | 105 | private async extendsRedundancy (redundancyModel: VideoRedundancyModel) { |
74 | // Refresh the video, maybe it was deleted | ||
75 | const video = await this.loadAndRefreshVideo(redundancyModel.VideoFile.Video.url) | ||
76 | |||
77 | if (!video) { | ||
78 | logger.info('Destroying existing redundancy %s, because the associated video does not exist anymore.', redundancyModel.url) | ||
79 | |||
80 | await redundancyModel.destroy() | ||
81 | return | ||
82 | } | ||
83 | |||
84 | const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) | 106 | const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) |
107 | // Redundancy strategy disabled, remove our redundancy instead of extending expiration | ||
108 | if (!redundancy) await removeVideoRedundancy(redundancyModel) | ||
109 | |||
85 | await this.extendsExpirationOf(redundancyModel, redundancy.minLifetime) | 110 | await this.extendsExpirationOf(redundancyModel, redundancy.minLifetime) |
86 | } | 111 | } |
87 | 112 | ||
@@ -112,49 +137,93 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
112 | } | 137 | } |
113 | } | 138 | } |
114 | 139 | ||
115 | private async createVideoRedundancy (redundancy: VideosRedundancy, filesToDuplicate: VideoFileModel[]) { | 140 | private async createVideoRedundancies (data: CandidateToDuplicate) { |
116 | const serverActor = await getServerActor() | 141 | const video = await this.loadAndRefreshVideo(data.video.url) |
142 | |||
143 | if (!video) { | ||
144 | logger.info('Video %s we want to duplicate does not existing anymore, skipping.', data.video.url) | ||
117 | 145 | ||
118 | for (const file of filesToDuplicate) { | 146 | return |
119 | const video = await this.loadAndRefreshVideo(file.Video.url) | 147 | } |
120 | 148 | ||
149 | for (const file of data.files) { | ||
121 | const existingRedundancy = await VideoRedundancyModel.loadLocalByFileId(file.id) | 150 | const existingRedundancy = await VideoRedundancyModel.loadLocalByFileId(file.id) |
122 | if (existingRedundancy) { | 151 | if (existingRedundancy) { |
123 | await this.extendsOrDeleteRedundancy(existingRedundancy) | 152 | await this.extendsRedundancy(existingRedundancy) |
124 | 153 | ||
125 | continue | 154 | continue |
126 | } | 155 | } |
127 | 156 | ||
128 | if (!video) { | 157 | await this.createVideoFileRedundancy(data.redundancy, video, file) |
129 | logger.info('Video %s we want to duplicate does not existing anymore, skipping.', file.Video.url) | 158 | } |
159 | |||
160 | for (const streamingPlaylist of data.streamingPlaylists) { | ||
161 | const existingRedundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(streamingPlaylist.id) | ||
162 | if (existingRedundancy) { | ||
163 | await this.extendsRedundancy(existingRedundancy) | ||
130 | 164 | ||
131 | continue | 165 | continue |
132 | } | 166 | } |
133 | 167 | ||
134 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, redundancy.strategy) | 168 | await this.createStreamingPlaylistRedundancy(data.redundancy, video, streamingPlaylist) |
169 | } | ||
170 | } | ||
135 | 171 | ||
136 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 172 | private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: VideoModel, file: VideoFileModel) { |
137 | const magnetUri = video.generateMagnetUri(file, baseUrlHttp, baseUrlWs) | 173 | file.Video = video |
138 | 174 | ||
139 | const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT) | 175 | const serverActor = await getServerActor() |
140 | 176 | ||
141 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) | 177 | logger.info('Duplicating %s - %d in videos redundancy with "%s" strategy.', video.url, file.resolution, redundancy.strategy) |
142 | await move(tmpPath, destPath) | ||
143 | 178 | ||
144 | const createdModel = await VideoRedundancyModel.create({ | 179 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
145 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 180 | const magnetUri = video.generateMagnetUri(file, baseUrlHttp, baseUrlWs) |
146 | url: getVideoCacheFileActivityPubUrl(file), | ||
147 | fileUrl: video.getVideoRedundancyUrl(file, CONFIG.WEBSERVER.URL), | ||
148 | strategy: redundancy.strategy, | ||
149 | videoFileId: file.id, | ||
150 | actorId: serverActor.id | ||
151 | }) | ||
152 | createdModel.VideoFile = file | ||
153 | 181 | ||
154 | await sendCreateCacheFile(serverActor, createdModel) | 182 | const tmpPath = await downloadWebTorrentVideo({ magnetUri }, VIDEO_IMPORT_TIMEOUT) |
155 | 183 | ||
156 | logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) | 184 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) |
157 | } | 185 | await move(tmpPath, destPath) |
186 | |||
187 | const createdModel = await VideoRedundancyModel.create({ | ||
188 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | ||
189 | url: getVideoCacheFileActivityPubUrl(file), | ||
190 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), | ||
191 | strategy: redundancy.strategy, | ||
192 | videoFileId: file.id, | ||
193 | actorId: serverActor.id | ||
194 | }) | ||
195 | |||
196 | createdModel.VideoFile = file | ||
197 | |||
198 | await sendCreateCacheFile(serverActor, video, createdModel) | ||
199 | |||
200 | logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) | ||
201 | } | ||
202 | |||
203 | private async createStreamingPlaylistRedundancy (redundancy: VideosRedundancy, video: VideoModel, playlist: VideoStreamingPlaylistModel) { | ||
204 | playlist.Video = video | ||
205 | |||
206 | const serverActor = await getServerActor() | ||
207 | |||
208 | logger.info('Duplicating %s streaming playlist in videos redundancy with "%s" strategy.', video.url, redundancy.strategy) | ||
209 | |||
210 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) | ||
211 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) | ||
212 | |||
213 | const createdModel = await VideoRedundancyModel.create({ | ||
214 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | ||
215 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), | ||
216 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), | ||
217 | strategy: redundancy.strategy, | ||
218 | videoStreamingPlaylistId: playlist.id, | ||
219 | actorId: serverActor.id | ||
220 | }) | ||
221 | |||
222 | createdModel.VideoStreamingPlaylist = playlist | ||
223 | |||
224 | await sendCreateCacheFile(serverActor, video, createdModel) | ||
225 | |||
226 | logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) | ||
158 | } | 227 | } |
159 | 228 | ||
160 | private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) { | 229 | private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) { |
@@ -168,8 +237,9 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
168 | await sendUpdateCacheFile(serverActor, redundancy) | 237 | await sendUpdateCacheFile(serverActor, redundancy) |
169 | } | 238 | } |
170 | 239 | ||
171 | private async purgeCacheIfNeeded (redundancy: VideosRedundancy, filesToDuplicate: VideoFileModel[]) { | 240 | private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) { |
172 | while (this.isTooHeavy(redundancy, filesToDuplicate)) { | 241 | while (this.isTooHeavy(candidateToDuplicate)) { |
242 | const redundancy = candidateToDuplicate.redundancy | ||
173 | const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime) | 243 | const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime) |
174 | if (!toDelete) return | 244 | if (!toDelete) return |
175 | 245 | ||
@@ -177,11 +247,11 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
177 | } | 247 | } |
178 | } | 248 | } |
179 | 249 | ||
180 | private async isTooHeavy (redundancy: VideosRedundancy, filesToDuplicate: VideoFileModel[]) { | 250 | private async isTooHeavy (candidateToDuplicate: CandidateToDuplicate) { |
181 | const maxSize = redundancy.size | 251 | const maxSize = candidateToDuplicate.redundancy.size |
182 | 252 | ||
183 | const totalDuplicated = await VideoRedundancyModel.getTotalDuplicated(redundancy.strategy) | 253 | const totalDuplicated = await VideoRedundancyModel.getTotalDuplicated(candidateToDuplicate.redundancy.strategy) |
184 | const totalWillDuplicate = totalDuplicated + this.getTotalFileSizes(filesToDuplicate) | 254 | const totalWillDuplicate = totalDuplicated + this.getTotalFileSizes(candidateToDuplicate.files, candidateToDuplicate.streamingPlaylists) |
185 | 255 | ||
186 | return totalWillDuplicate > maxSize | 256 | return totalWillDuplicate > maxSize |
187 | } | 257 | } |
@@ -191,13 +261,15 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
191 | } | 261 | } |
192 | 262 | ||
193 | private buildEntryLogId (object: VideoRedundancyModel) { | 263 | private buildEntryLogId (object: VideoRedundancyModel) { |
194 | return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` | 264 | if (object.VideoFile) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` |
265 | |||
266 | return `${object.VideoStreamingPlaylist.playlistUrl}` | ||
195 | } | 267 | } |
196 | 268 | ||
197 | private getTotalFileSizes (files: VideoFileModel[]) { | 269 | private getTotalFileSizes (files: VideoFileModel[], playlists: VideoStreamingPlaylistModel[]) { |
198 | const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size | 270 | const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size |
199 | 271 | ||
200 | return files.reduce(fileReducer, 0) | 272 | return files.reduce(fileReducer, 0) * playlists.length |
201 | } | 273 | } |
202 | 274 | ||
203 | private async loadAndRefreshVideo (videoUrl: string) { | 275 | private async loadAndRefreshVideo (videoUrl: string) { |
diff --git a/server/lib/schedulers/youtube-dl-update-scheduler.ts b/server/lib/schedulers/youtube-dl-update-scheduler.ts index aa027116d..aefe6aba4 100644 --- a/server/lib/schedulers/youtube-dl-update-scheduler.ts +++ b/server/lib/schedulers/youtube-dl-update-scheduler.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import { AbstractScheduler } from './abstract-scheduler' | 1 | import { AbstractScheduler } from './abstract-scheduler' |
2 | import { SCHEDULER_INTERVALS_MS } from '../../initializers' | 2 | import { SCHEDULER_INTERVALS_MS } from '../../initializers/constants' |
3 | import { updateYoutubeDLBinary } from '../../helpers/youtube-dl' | 3 | import { updateYoutubeDLBinary } from '../../helpers/youtube-dl' |
4 | 4 | ||
5 | export class YoutubeDlUpdateScheduler extends AbstractScheduler { | 5 | export class YoutubeDlUpdateScheduler extends AbstractScheduler { |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts new file mode 100644 index 000000000..950b14c3b --- /dev/null +++ b/server/lib/thumbnail.ts | |||
@@ -0,0 +1,151 @@ | |||
1 | import { VideoFileModel } from '../models/video/video-file' | ||
2 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' | ||
3 | import { CONFIG } from '../initializers/config' | ||
4 | import { PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' | ||
5 | import { VideoModel } from '../models/video/video' | ||
6 | import { ThumbnailModel } from '../models/video/thumbnail' | ||
7 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' | ||
8 | import { processImage } from '../helpers/image-utils' | ||
9 | import { join } from 'path' | ||
10 | import { downloadImage } from '../helpers/requests' | ||
11 | import { VideoPlaylistModel } from '../models/video/video-playlist' | ||
12 | |||
13 | type ImageSize = { height: number, width: number } | ||
14 | |||
15 | function createPlaylistMiniatureFromExisting (inputPath: string, playlist: VideoPlaylistModel, keepOriginal = false, size?: ImageSize) { | ||
16 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | ||
17 | const type = ThumbnailType.MINIATURE | ||
18 | |||
19 | const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height }, keepOriginal) | ||
20 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) | ||
21 | } | ||
22 | |||
23 | function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) { | ||
24 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | ||
25 | const type = ThumbnailType.MINIATURE | ||
26 | |||
27 | const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) | ||
28 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | ||
29 | } | ||
30 | |||
31 | function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) { | ||
32 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | ||
33 | const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) | ||
34 | |||
35 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | ||
36 | } | ||
37 | |||
38 | function createVideoMiniatureFromExisting (inputPath: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) { | ||
39 | const { filename, outputPath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | ||
40 | const thumbnailCreator = () => processImage(inputPath, outputPath, { width, height }) | ||
41 | |||
42 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) | ||
43 | } | ||
44 | |||
45 | function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { | ||
46 | const input = video.getVideoFilePath(videoFile) | ||
47 | |||
48 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type) | ||
49 | const thumbnailCreator = () => generateImageFromVideoFile(input, basePath, filename, { height, width }) | ||
50 | |||
51 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail }) | ||
52 | } | ||
53 | |||
54 | function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) { | ||
55 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | ||
56 | |||
57 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() | ||
58 | |||
59 | thumbnail.filename = filename | ||
60 | thumbnail.height = height | ||
61 | thumbnail.width = width | ||
62 | thumbnail.type = type | ||
63 | thumbnail.fileUrl = fileUrl | ||
64 | |||
65 | return thumbnail | ||
66 | } | ||
67 | |||
68 | // --------------------------------------------------------------------------- | ||
69 | |||
70 | export { | ||
71 | generateVideoMiniature, | ||
72 | createVideoMiniatureFromUrl, | ||
73 | createVideoMiniatureFromExisting, | ||
74 | createPlaceholderThumbnail, | ||
75 | createPlaylistMiniatureFromUrl, | ||
76 | createPlaylistMiniatureFromExisting | ||
77 | } | ||
78 | |||
79 | function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) { | ||
80 | const filename = playlist.generateThumbnailName() | ||
81 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | ||
82 | |||
83 | return { | ||
84 | filename, | ||
85 | basePath, | ||
86 | existingThumbnail: playlist.Thumbnail, | ||
87 | outputPath: join(basePath, filename), | ||
88 | height: size ? size.height : THUMBNAILS_SIZE.height, | ||
89 | width: size ? size.width : THUMBNAILS_SIZE.width | ||
90 | } | ||
91 | } | ||
92 | |||
93 | function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: ImageSize) { | ||
94 | const existingThumbnail = Array.isArray(video.Thumbnails) | ||
95 | ? video.Thumbnails.find(t => t.type === type) | ||
96 | : undefined | ||
97 | |||
98 | if (type === ThumbnailType.MINIATURE) { | ||
99 | const filename = video.generateThumbnailName() | ||
100 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | ||
101 | |||
102 | return { | ||
103 | filename, | ||
104 | basePath, | ||
105 | existingThumbnail, | ||
106 | outputPath: join(basePath, filename), | ||
107 | height: size ? size.height : THUMBNAILS_SIZE.height, | ||
108 | width: size ? size.width : THUMBNAILS_SIZE.width | ||
109 | } | ||
110 | } | ||
111 | |||
112 | if (type === ThumbnailType.PREVIEW) { | ||
113 | const filename = video.generatePreviewName() | ||
114 | const basePath = CONFIG.STORAGE.PREVIEWS_DIR | ||
115 | |||
116 | return { | ||
117 | filename, | ||
118 | basePath, | ||
119 | existingThumbnail, | ||
120 | outputPath: join(basePath, filename), | ||
121 | height: size ? size.height : PREVIEWS_SIZE.height, | ||
122 | width: size ? size.width : PREVIEWS_SIZE.width | ||
123 | } | ||
124 | } | ||
125 | |||
126 | return undefined | ||
127 | } | ||
128 | |||
129 | async function createThumbnailFromFunction (parameters: { | ||
130 | thumbnailCreator: () => Promise<any>, | ||
131 | filename: string, | ||
132 | height: number, | ||
133 | width: number, | ||
134 | type: ThumbnailType, | ||
135 | fileUrl?: string, | ||
136 | existingThumbnail?: ThumbnailModel | ||
137 | }) { | ||
138 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, fileUrl = null } = parameters | ||
139 | |||
140 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() | ||
141 | |||
142 | thumbnail.filename = filename | ||
143 | thumbnail.height = height | ||
144 | thumbnail.width = width | ||
145 | thumbnail.type = type | ||
146 | thumbnail.fileUrl = fileUrl | ||
147 | |||
148 | await thumbnailCreator() | ||
149 | |||
150 | return thumbnail | ||
151 | } | ||
diff --git a/server/lib/user.ts b/server/lib/user.ts index a39ef6c3d..7badb3e72 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -1,18 +1,19 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as uuidv4 from 'uuid/v4' | 2 | import * as uuidv4 from 'uuid/v4' |
3 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 3 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
4 | import { sequelizeTypescript, SERVER_ACTOR_NAME } from '../initializers' | 4 | import { SERVER_ACTOR_NAME } from '../initializers/constants' |
5 | import { AccountModel } from '../models/account/account' | 5 | import { AccountModel } from '../models/account/account' |
6 | import { UserModel } from '../models/account/user' | 6 | import { UserModel } from '../models/account/user' |
7 | import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' | 7 | import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' |
8 | import { createVideoChannel } from './video-channel' | 8 | import { createVideoChannel } from './video-channel' |
9 | import { VideoChannelModel } from '../models/video/video-channel' | 9 | import { VideoChannelModel } from '../models/video/video-channel' |
10 | import { FilteredModelAttributes } from 'sequelize-typescript/lib/models/Model' | ||
11 | import { ActorModel } from '../models/activitypub/actor' | 10 | import { ActorModel } from '../models/activitypub/actor' |
12 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | 11 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' |
13 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' | 12 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' |
13 | import { createWatchLaterPlaylist } from './video-playlist' | ||
14 | import { sequelizeTypescript } from '../initializers/database' | ||
14 | 15 | ||
15 | async function createUserAccountAndChannel (userToCreate: UserModel, validateUser = true) { | 16 | async function createUserAccountAndChannelAndPlaylist (userToCreate: UserModel, validateUser = true) { |
16 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { | 17 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { |
17 | const userOptions = { | 18 | const userOptions = { |
18 | transaction: t, | 19 | transaction: t, |
@@ -38,7 +39,9 @@ async function createUserAccountAndChannel (userToCreate: UserModel, validateUse | |||
38 | } | 39 | } |
39 | const videoChannel = await createVideoChannel(videoChannelInfo, accountCreated, t) | 40 | const videoChannel = await createVideoChannel(videoChannelInfo, accountCreated, t) |
40 | 41 | ||
41 | return { user: userCreated, account: accountCreated, videoChannel } | 42 | const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) |
43 | |||
44 | return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist } | ||
42 | }) | 45 | }) |
43 | 46 | ||
44 | const [ accountKeys, channelKeys ] = await Promise.all([ | 47 | const [ accountKeys, channelKeys ] = await Promise.all([ |
@@ -69,7 +72,7 @@ async function createLocalAccountWithoutKeys ( | |||
69 | userId, | 72 | userId, |
70 | applicationId, | 73 | applicationId, |
71 | actorId: actorInstanceCreated.id | 74 | actorId: actorInstanceCreated.id |
72 | } as FilteredModelAttributes<AccountModel>) | 75 | }) |
73 | 76 | ||
74 | const accountInstanceCreated = await accountInstance.save({ transaction: t }) | 77 | const accountInstanceCreated = await accountInstance.save({ transaction: t }) |
75 | accountInstanceCreated.Actor = actorInstanceCreated | 78 | accountInstanceCreated.Actor = actorInstanceCreated |
@@ -89,7 +92,7 @@ async function createApplicationActor (applicationId: number) { | |||
89 | 92 | ||
90 | export { | 93 | export { |
91 | createApplicationActor, | 94 | createApplicationActor, |
92 | createUserAccountAndChannel, | 95 | createUserAccountAndChannelAndPlaylist, |
93 | createLocalAccountWithoutKeys | 96 | createLocalAccountWithoutKeys |
94 | } | 97 | } |
95 | 98 | ||
@@ -103,10 +106,12 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Sequelize.Tr | |||
103 | myVideoImportFinished: UserNotificationSettingValue.WEB, | 106 | myVideoImportFinished: UserNotificationSettingValue.WEB, |
104 | myVideoPublished: UserNotificationSettingValue.WEB, | 107 | myVideoPublished: UserNotificationSettingValue.WEB, |
105 | videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 108 | videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
109 | videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
106 | blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 110 | blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
107 | newUserRegistration: UserNotificationSettingValue.WEB, | 111 | newUserRegistration: UserNotificationSettingValue.WEB, |
108 | commentMention: UserNotificationSettingValue.WEB, | 112 | commentMention: UserNotificationSettingValue.WEB, |
109 | newFollow: UserNotificationSettingValue.WEB | 113 | newFollow: UserNotificationSettingValue.WEB, |
114 | newInstanceFollower: UserNotificationSettingValue.WEB | ||
110 | } | 115 | } |
111 | 116 | ||
112 | return UserNotificationSettingModel.create(values, { transaction: t }) | 117 | return UserNotificationSettingModel.create(values, { transaction: t }) |
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts new file mode 100644 index 000000000..985b89e31 --- /dev/null +++ b/server/lib/video-blacklist.ts | |||
@@ -0,0 +1,33 @@ | |||
1 | import * as sequelize from 'sequelize' | ||
2 | import { CONFIG } from '../initializers/config' | ||
3 | import { UserRight, VideoBlacklistType } from '../../shared/models' | ||
4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
5 | import { UserModel } from '../models/account/user' | ||
6 | import { VideoModel } from '../models/video/video' | ||
7 | import { logger } from '../helpers/logger' | ||
8 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' | ||
9 | |||
10 | async function autoBlacklistVideoIfNeeded (video: VideoModel, user: UserModel, transaction: sequelize.Transaction) { | ||
11 | if (!CONFIG.AUTO_BLACKLIST.VIDEOS.OF_USERS.ENABLED) return false | ||
12 | |||
13 | if (user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST) || user.hasAdminFlag(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST)) return false | ||
14 | |||
15 | const sequelizeOptions = { transaction } | ||
16 | const videoBlacklistToCreate = { | ||
17 | videoId: video.id, | ||
18 | unfederated: true, | ||
19 | reason: 'Auto-blacklisted. Moderator review required.', | ||
20 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | ||
21 | } | ||
22 | await VideoBlacklistModel.create(videoBlacklistToCreate, sequelizeOptions) | ||
23 | |||
24 | logger.info('Video %s auto-blacklisted.', video.uuid) | ||
25 | |||
26 | return true | ||
27 | } | ||
28 | |||
29 | // --------------------------------------------------------------------------- | ||
30 | |||
31 | export { | ||
32 | autoBlacklistVideoIfNeeded | ||
33 | } | ||
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index 59bce7520..bfe22d225 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts | |||
@@ -28,7 +28,7 @@ async function createVideoComment (obj: { | |||
28 | videoId: obj.video.id, | 28 | videoId: obj.video.id, |
29 | accountId: obj.account.id, | 29 | accountId: obj.account.id, |
30 | url: 'fake url' | 30 | url: 'fake url' |
31 | }, { transaction: t, validate: false }) | 31 | }, { transaction: t, validate: false } as any) // FIXME: sequelize typings |
32 | 32 | ||
33 | comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment)) | 33 | comment.set('url', getVideoCommentActivityPubUrl(obj.video, comment)) |
34 | 34 | ||
diff --git a/server/lib/video-playlist.ts b/server/lib/video-playlist.ts new file mode 100644 index 000000000..6e214e60f --- /dev/null +++ b/server/lib/video-playlist.ts | |||
@@ -0,0 +1,29 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { AccountModel } from '../models/account/account' | ||
3 | import { VideoPlaylistModel } from '../models/video/video-playlist' | ||
4 | import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
5 | import { getVideoPlaylistActivityPubUrl } from './activitypub' | ||
6 | import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' | ||
7 | |||
8 | async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) { | ||
9 | const videoPlaylist = new VideoPlaylistModel({ | ||
10 | name: 'Watch later', | ||
11 | privacy: VideoPlaylistPrivacy.PRIVATE, | ||
12 | type: VideoPlaylistType.WATCH_LATER, | ||
13 | ownerAccountId: account.id | ||
14 | }) | ||
15 | |||
16 | videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object | ||
17 | |||
18 | await videoPlaylist.save({ transaction: t }) | ||
19 | |||
20 | videoPlaylist.OwnerAccount = account | ||
21 | |||
22 | return videoPlaylist | ||
23 | } | ||
24 | |||
25 | // --------------------------------------------------------------------------- | ||
26 | |||
27 | export { | ||
28 | createWatchLaterPlaylist | ||
29 | } | ||
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index 4460f46e4..0fe0ff12a 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -1,11 +1,15 @@ | |||
1 | import { CONFIG } from '../initializers' | 1 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION, WEBSERVER } from '../initializers/constants' |
2 | import { extname, join } from 'path' | 2 | import { join } from 'path' |
3 | import { getVideoFileFPS, getVideoFileResolution, transcode } from '../helpers/ffmpeg-utils' | 3 | import { getVideoFileFPS, transcode } from '../helpers/ffmpeg-utils' |
4 | import { copy, remove, move, stat } from 'fs-extra' | 4 | import { ensureDir, move, remove, stat } from 'fs-extra' |
5 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { VideoResolution } from '../../shared/models/videos' |
7 | import { VideoFileModel } from '../models/video/video-file' | 7 | import { VideoFileModel } from '../models/video/video-file' |
8 | import { VideoModel } from '../models/video/video' | 8 | import { VideoModel } from '../models/video/video' |
9 | import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' | ||
10 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
11 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | ||
12 | import { CONFIG } from '../initializers/config' | ||
9 | 13 | ||
10 | async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { | 14 | async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { |
11 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 15 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
@@ -17,7 +21,8 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi | |||
17 | 21 | ||
18 | const transcodeOptions = { | 22 | const transcodeOptions = { |
19 | inputPath: videoInputPath, | 23 | inputPath: videoInputPath, |
20 | outputPath: videoTranscodedPath | 24 | outputPath: videoTranscodedPath, |
25 | resolution: inputVideoFile.resolution | ||
21 | } | 26 | } |
22 | 27 | ||
23 | // Could be very long! | 28 | // Could be very long! |
@@ -47,7 +52,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi | |||
47 | } | 52 | } |
48 | } | 53 | } |
49 | 54 | ||
50 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { | 55 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { |
51 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 56 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
52 | const extname = '.mp4' | 57 | const extname = '.mp4' |
53 | 58 | ||
@@ -60,13 +65,13 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR | |||
60 | size: 0, | 65 | size: 0, |
61 | videoId: video.id | 66 | videoId: video.id |
62 | }) | 67 | }) |
63 | const videoOutputPath = join(videosDirectory, video.getVideoFilename(newVideoFile)) | 68 | const videoOutputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(newVideoFile)) |
64 | 69 | ||
65 | const transcodeOptions = { | 70 | const transcodeOptions = { |
66 | inputPath: videoInputPath, | 71 | inputPath: videoInputPath, |
67 | outputPath: videoOutputPath, | 72 | outputPath: videoOutputPath, |
68 | resolution, | 73 | resolution, |
69 | isPortraitMode | 74 | isPortraitMode: isPortrait |
70 | } | 75 | } |
71 | 76 | ||
72 | await transcode(transcodeOptions) | 77 | await transcode(transcodeOptions) |
@@ -84,48 +89,44 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR | |||
84 | video.VideoFiles.push(newVideoFile) | 89 | video.VideoFiles.push(newVideoFile) |
85 | } | 90 | } |
86 | 91 | ||
87 | async function importVideoFile (video: VideoModel, inputFilePath: string) { | 92 | async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { |
88 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) | 93 | const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) |
89 | const { size } = await stat(inputFilePath) | 94 | await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) |
90 | const fps = await getVideoFileFPS(inputFilePath) | ||
91 | 95 | ||
92 | let updatedVideoFile = new VideoFileModel({ | 96 | const videoInputPath = join(CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename(video.getOriginalFile())) |
93 | resolution: videoFileResolution, | 97 | const outputPath = join(baseHlsDirectory, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) |
94 | extname: extname(inputFilePath), | ||
95 | size, | ||
96 | fps, | ||
97 | videoId: video.id | ||
98 | }) | ||
99 | |||
100 | const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) | ||
101 | 98 | ||
102 | if (currentVideoFile) { | 99 | const transcodeOptions = { |
103 | // Remove old file and old torrent | 100 | inputPath: videoInputPath, |
104 | await video.removeFile(currentVideoFile) | 101 | outputPath, |
105 | await video.removeTorrent(currentVideoFile) | 102 | resolution, |
106 | // Remove the old video file from the array | 103 | isPortraitMode, |
107 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) | ||
108 | |||
109 | // Update the database | ||
110 | currentVideoFile.set('extname', updatedVideoFile.extname) | ||
111 | currentVideoFile.set('size', updatedVideoFile.size) | ||
112 | currentVideoFile.set('fps', updatedVideoFile.fps) | ||
113 | 104 | ||
114 | updatedVideoFile = currentVideoFile | 105 | hlsPlaylist: { |
106 | videoFilename: VideoStreamingPlaylistModel.getHlsVideoName(video.uuid, resolution) | ||
107 | } | ||
115 | } | 108 | } |
116 | 109 | ||
117 | const outputPath = video.getVideoFilePath(updatedVideoFile) | 110 | await transcode(transcodeOptions) |
118 | await copy(inputFilePath, outputPath) | ||
119 | 111 | ||
120 | await video.createTorrentAndSetInfoHash(updatedVideoFile) | 112 | await updateMasterHLSPlaylist(video) |
113 | await updateSha256Segments(video) | ||
121 | 114 | ||
122 | await updatedVideoFile.save() | 115 | const playlistUrl = WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsMasterPlaylistStaticPath(video.uuid) |
123 | 116 | ||
124 | video.VideoFiles.push(updatedVideoFile) | 117 | await VideoStreamingPlaylistModel.upsert({ |
118 | videoId: video.id, | ||
119 | playlistUrl, | ||
120 | segmentsSha256Url: WEBSERVER.URL + VideoStreamingPlaylistModel.getHlsSha256SegmentsStaticPath(video.uuid), | ||
121 | p2pMediaLoaderInfohashes: VideoStreamingPlaylistModel.buildP2PMediaLoaderInfoHashes(playlistUrl, video.VideoFiles), | ||
122 | p2pMediaLoaderPeerVersion: P2P_MEDIA_LOADER_PEER_VERSION, | ||
123 | |||
124 | type: VideoStreamingPlaylistType.HLS | ||
125 | }) | ||
125 | } | 126 | } |
126 | 127 | ||
127 | export { | 128 | export { |
129 | generateHlsPlaylist, | ||
128 | optimizeVideofile, | 130 | optimizeVideofile, |
129 | transcodeOriginalVideofile, | 131 | transcodeOriginalVideofile |
130 | importVideoFile | ||
131 | } | 132 | } |
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index 01e5dd24e..b1e5b5236 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts | |||
@@ -1,11 +1,9 @@ | |||
1 | import { eachSeries } from 'async' | 1 | import { NextFunction, Request, Response } from 'express' |
2 | import { NextFunction, Request, RequestHandler, Response } from 'express' | ||
3 | import { ActivityPubSignature } from '../../shared' | 2 | import { ActivityPubSignature } from '../../shared' |
4 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
5 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' | 4 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../helpers/peertube-crypto' |
6 | import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers' | 5 | import { ACCEPT_HEADERS, ACTIVITY_PUB, HTTP_SIGNATURE } from '../initializers/constants' |
7 | import { getOrCreateActorAndServerAndModel } from '../lib/activitypub' | 6 | import { getOrCreateActorAndServerAndModel } from '../lib/activitypub' |
8 | import { ActorModel } from '../models/activitypub/actor' | ||
9 | import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger' | 7 | import { loadActorUrlOrGetFromWebfinger } from '../helpers/webfinger' |
10 | 8 | ||
11 | async function checkSignature (req: Request, res: Response, next: NextFunction) { | 9 | async function checkSignature (req: Request, res: Response, next: NextFunction) { |
@@ -13,7 +11,7 @@ async function checkSignature (req: Request, res: Response, next: NextFunction) | |||
13 | const httpSignatureChecked = await checkHttpSignature(req, res) | 11 | const httpSignatureChecked = await checkHttpSignature(req, res) |
14 | if (httpSignatureChecked !== true) return | 12 | if (httpSignatureChecked !== true) return |
15 | 13 | ||
16 | const actor: ActorModel = res.locals.signature.actor | 14 | const actor = res.locals.signature.actor |
17 | 15 | ||
18 | // Forwarded activity | 16 | // Forwarded activity |
19 | const bodyActor = req.body.actor | 17 | const bodyActor = req.body.actor |
@@ -30,23 +28,16 @@ async function checkSignature (req: Request, res: Response, next: NextFunction) | |||
30 | } | 28 | } |
31 | } | 29 | } |
32 | 30 | ||
33 | function executeIfActivityPub (fun: RequestHandler | RequestHandler[]) { | 31 | function executeIfActivityPub (req: Request, res: Response, next: NextFunction) { |
34 | return (req: Request, res: Response, next: NextFunction) => { | 32 | const accepted = req.accepts(ACCEPT_HEADERS) |
35 | const accepted = req.accepts(ACCEPT_HEADERS) | 33 | if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.indexOf(accepted) === -1) { |
36 | if (accepted === false || ACTIVITY_PUB.POTENTIAL_ACCEPT_HEADERS.indexOf(accepted) === -1) { | 34 | // Bypass this route |
37 | return next() | 35 | return next('route') |
38 | } | 36 | } |
39 | |||
40 | logger.debug('ActivityPub request for %s.', req.url) | ||
41 | 37 | ||
42 | if (Array.isArray(fun) === true) { | 38 | logger.debug('ActivityPub request for %s.', req.url) |
43 | return eachSeries(fun as RequestHandler[], (f, cb) => { | ||
44 | f(req, res, cb) | ||
45 | }, next) | ||
46 | } | ||
47 | 39 | ||
48 | return (fun as RequestHandler)(req, res, next) | 40 | return next() |
49 | } | ||
50 | } | 41 | } |
51 | 42 | ||
52 | // --------------------------------------------------------------------------- | 43 | // --------------------------------------------------------------------------- |
@@ -83,6 +74,8 @@ async function checkHttpSignature (req: Request, res: Response) { | |||
83 | 74 | ||
84 | const verified = isHTTPSignatureVerified(parsed, actor) | 75 | const verified = isHTTPSignatureVerified(parsed, actor) |
85 | if (verified !== true) { | 76 | if (verified !== true) { |
77 | logger.warn('Signature from %s is invalid', actorUrl, { parsed }) | ||
78 | |||
86 | res.sendStatus(403) | 79 | res.sendStatus(403) |
87 | return false | 80 | return false |
88 | } | 81 | } |
diff --git a/server/middlewares/cache.ts b/server/middlewares/cache.ts index 8ffe75700..ef8611875 100644 --- a/server/middlewares/cache.ts +++ b/server/middlewares/cache.ts | |||
@@ -1,72 +1,16 @@ | |||
1 | import * as express from 'express' | ||
2 | import * as AsyncLock from 'async-lock' | ||
3 | import { parseDuration } from '../helpers/core-utils' | ||
4 | import { Redis } from '../lib/redis' | 1 | import { Redis } from '../lib/redis' |
5 | import { logger } from '../helpers/logger' | 2 | import * as apicache from 'apicache' |
6 | 3 | ||
7 | const lock = new AsyncLock({ timeout: 5000 }) | 4 | // Ensure Redis is initialized |
5 | Redis.Instance.init() | ||
8 | 6 | ||
9 | function cacheRoute (lifetimeArg: string | number) { | 7 | const options = { |
10 | return async function (req: express.Request, res: express.Response, next: express.NextFunction) { | 8 | redisClient: Redis.Instance.getClient(), |
11 | const redisKey = Redis.Instance.generateCachedRouteKey(req) | 9 | appendKey: () => Redis.Instance.getPrefix() |
12 | |||
13 | try { | ||
14 | await lock.acquire(redisKey, async (done) => { | ||
15 | const cached = await Redis.Instance.getCachedRoute(req) | ||
16 | |||
17 | // Not cached | ||
18 | if (!cached) { | ||
19 | logger.debug('No cached results for route %s.', req.originalUrl) | ||
20 | |||
21 | const sendSave = res.send.bind(res) | ||
22 | const redirectSave = res.redirect.bind(res) | ||
23 | |||
24 | res.send = (body) => { | ||
25 | if (res.statusCode >= 200 && res.statusCode < 400) { | ||
26 | const contentType = res.get('content-type') | ||
27 | const lifetime = parseDuration(lifetimeArg) | ||
28 | |||
29 | Redis.Instance.setCachedRoute(req, body, lifetime, contentType, res.statusCode) | ||
30 | .then(() => done()) | ||
31 | .catch(err => { | ||
32 | logger.error('Cannot cache route.', { err }) | ||
33 | return done(err) | ||
34 | }) | ||
35 | } else { | ||
36 | done() | ||
37 | } | ||
38 | |||
39 | return sendSave(body) | ||
40 | } | ||
41 | |||
42 | res.redirect = url => { | ||
43 | done() | ||
44 | |||
45 | return redirectSave(url) | ||
46 | } | ||
47 | |||
48 | return next() | ||
49 | } | ||
50 | |||
51 | if (cached.contentType) res.set('content-type', cached.contentType) | ||
52 | |||
53 | if (cached.statusCode) { | ||
54 | const statusCode = parseInt(cached.statusCode, 10) | ||
55 | if (!isNaN(statusCode)) res.status(statusCode) | ||
56 | } | ||
57 | |||
58 | logger.debug('Use cached result for %s.', req.originalUrl) | ||
59 | res.send(cached.body).end() | ||
60 | |||
61 | return done() | ||
62 | }) | ||
63 | } catch (err) { | ||
64 | logger.error('Cannot serve cached route.', { err }) | ||
65 | return next() | ||
66 | } | ||
67 | } | ||
68 | } | 10 | } |
69 | 11 | ||
12 | const cacheRoute = apicache.options(options).middleware | ||
13 | |||
70 | // --------------------------------------------------------------------------- | 14 | // --------------------------------------------------------------------------- |
71 | 15 | ||
72 | export { | 16 | export { |
diff --git a/server/middlewares/csp.ts b/server/middlewares/csp.ts index 8b919af0d..d484b3021 100644 --- a/server/middlewares/csp.ts +++ b/server/middlewares/csp.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as helmet from 'helmet' | 1 | import * as helmet from 'helmet' |
2 | import { CONFIG } from '../initializers/constants' | 2 | import { CONFIG } from '../initializers/config' |
3 | 3 | ||
4 | const baseDirectives = Object.assign({}, | 4 | const baseDirectives = Object.assign({}, |
5 | { | 5 | { |
@@ -16,24 +16,22 @@ const baseDirectives = Object.assign({}, | |||
16 | baseUri: ["'self'"], | 16 | baseUri: ["'self'"], |
17 | manifestSrc: ["'self'"], | 17 | manifestSrc: ["'self'"], |
18 | frameSrc: ["'self'"], // instead of deprecated child-src / self because of test-embed | 18 | frameSrc: ["'self'"], // instead of deprecated child-src / self because of test-embed |
19 | workerSrc: ["'self'"] // instead of deprecated child-src | 19 | workerSrc: ["'self'", 'blob:'] // instead of deprecated child-src |
20 | }, | 20 | }, |
21 | CONFIG.SERVICES['CSP-LOGGER'] ? { reportUri: CONFIG.SERVICES['CSP-LOGGER'] } : {}, | 21 | CONFIG.CSP.REPORT_URI ? { reportUri: CONFIG.CSP.REPORT_URI } : {}, |
22 | CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {} | 22 | CONFIG.WEBSERVER.SCHEME === 'https' ? { upgradeInsecureRequests: true } : {} |
23 | ) | 23 | ) |
24 | 24 | ||
25 | const baseCSP = helmet.contentSecurityPolicy({ | 25 | const baseCSP = helmet.contentSecurityPolicy({ |
26 | directives: baseDirectives, | 26 | directives: baseDirectives, |
27 | browserSniff: false, | 27 | browserSniff: false, |
28 | reportOnly: true | 28 | reportOnly: CONFIG.CSP.REPORT_ONLY |
29 | }) | 29 | }) |
30 | 30 | ||
31 | const embedCSP = helmet.contentSecurityPolicy({ | 31 | const embedCSP = helmet.contentSecurityPolicy({ |
32 | directives: Object.assign(baseDirectives, { | 32 | directives: Object.assign({}, baseDirectives, { frameAncestors: ['*'] }), |
33 | frameAncestors: ['*'] | ||
34 | }), | ||
35 | browserSniff: false, // assumes a modern browser, but allows CDN in front | 33 | browserSniff: false, // assumes a modern browser, but allows CDN in front |
36 | reportOnly: true | 34 | reportOnly: CONFIG.CSP.REPORT_ONLY |
37 | }) | 35 | }) |
38 | 36 | ||
39 | // --------------------------------------------------------------------------- | 37 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/oauth.ts b/server/middlewares/oauth.ts index 1d193d467..2b4e300e4 100644 --- a/server/middlewares/oauth.ts +++ b/server/middlewares/oauth.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as OAuthServer from 'express-oauth-server' | 2 | import * as OAuthServer from 'express-oauth-server' |
3 | import 'express-validator' | 3 | import 'express-validator' |
4 | import { OAUTH_LIFETIME } from '../initializers' | 4 | import { OAUTH_LIFETIME } from '../initializers/constants' |
5 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
6 | import { Socket } from 'socket.io' | 6 | import { Socket } from 'socket.io' |
7 | import { getAccessToken } from '../lib/oauth-model' | 7 | import { getAccessToken } from '../lib/oauth-model' |
@@ -35,6 +35,8 @@ function authenticateSocket (socket: Socket, next: (err?: any) => void) { | |||
35 | 35 | ||
36 | logger.debug('Checking socket access token %s.', accessToken) | 36 | logger.debug('Checking socket access token %s.', accessToken) |
37 | 37 | ||
38 | if (!accessToken) return next(new Error('No access token provided')) | ||
39 | |||
38 | getAccessToken(accessToken) | 40 | getAccessToken(accessToken) |
39 | .then(tokenDB => { | 41 | .then(tokenDB => { |
40 | const now = new Date() | 42 | const now = new Date() |
diff --git a/server/middlewares/pagination.ts b/server/middlewares/pagination.ts index 9b497b19e..83304940f 100644 --- a/server/middlewares/pagination.ts +++ b/server/middlewares/pagination.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import 'express-validator' | 1 | import 'express-validator' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | 3 | ||
4 | import { PAGINATION } from '../initializers' | 4 | import { PAGINATION } from '../initializers/constants' |
5 | 5 | ||
6 | function setDefaultPagination (req: express.Request, res: express.Response, next: express.NextFunction) { | 6 | function setDefaultPagination (req: express.Request, res: express.Response, next: express.NextFunction) { |
7 | if (!req.query.start) req.query.start = 0 | 7 | if (!req.query.start) req.query.start = 0 |
diff --git a/server/middlewares/user-right.ts b/server/middlewares/user-right.ts index 7cea7aa1e..498e3d677 100644 --- a/server/middlewares/user-right.ts +++ b/server/middlewares/user-right.ts | |||
@@ -2,11 +2,10 @@ import * as express from 'express' | |||
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { UserRight } from '../../shared' | 3 | import { UserRight } from '../../shared' |
4 | import { logger } from '../helpers/logger' | 4 | import { logger } from '../helpers/logger' |
5 | import { UserModel } from '../models/account/user' | ||
6 | 5 | ||
7 | function ensureUserHasRight (userRight: UserRight) { | 6 | function ensureUserHasRight (userRight: UserRight) { |
8 | return function (req: express.Request, res: express.Response, next: express.NextFunction) { | 7 | return function (req: express.Request, res: express.Response, next: express.NextFunction) { |
9 | const user = res.locals.oauth.token.user as UserModel | 8 | const user = res.locals.oauth.token.user |
10 | if (user.hasRight(userRight) === false) { | 9 | if (user.hasRight(userRight) === false) { |
11 | const message = `User ${user.username} does not have right ${UserRight[userRight]} to access to ${req.path}.` | 10 | const message = `User ${user.username} does not have right ${UserRight[userRight]} to access to ${req.path}.` |
12 | logger.info(message) | 11 | logger.info(message) |
diff --git a/server/middlewares/validators/account.ts b/server/middlewares/validators/account.ts index b3a51e631..96e120a38 100644 --- a/server/middlewares/validators/account.ts +++ b/server/middlewares/validators/account.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param } from 'express-validator/check' | 2 | import { param } from 'express-validator/check' |
3 | import { isAccountNameValid, isAccountNameWithHostExist, isLocalAccountNameExist } from '../../helpers/custom-validators/accounts' | 3 | import { isAccountNameValid, doesAccountNameWithHostExist, doesLocalAccountNameExist } from '../../helpers/custom-validators/accounts' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { areValidationErrors } from './utils' | 5 | import { areValidationErrors } from './utils' |
6 | 6 | ||
@@ -11,20 +11,20 @@ const localAccountValidator = [ | |||
11 | logger.debug('Checking localAccountValidator parameters', { parameters: req.params }) | 11 | logger.debug('Checking localAccountValidator parameters', { parameters: req.params }) |
12 | 12 | ||
13 | if (areValidationErrors(req, res)) return | 13 | if (areValidationErrors(req, res)) return |
14 | if (!await isLocalAccountNameExist(req.params.name, res)) return | 14 | if (!await doesLocalAccountNameExist(req.params.name, res)) return |
15 | 15 | ||
16 | return next() | 16 | return next() |
17 | } | 17 | } |
18 | ] | 18 | ] |
19 | 19 | ||
20 | const accountsNameWithHostGetValidator = [ | 20 | const accountNameWithHostGetValidator = [ |
21 | param('accountName').exists().withMessage('Should have an account name with host'), | 21 | param('accountName').exists().withMessage('Should have an account name with host'), |
22 | 22 | ||
23 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 23 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
24 | logger.debug('Checking accountsNameWithHostGetValidator parameters', { parameters: req.params }) | 24 | logger.debug('Checking accountsNameWithHostGetValidator parameters', { parameters: req.params }) |
25 | 25 | ||
26 | if (areValidationErrors(req, res)) return | 26 | if (areValidationErrors(req, res)) return |
27 | if (!await isAccountNameWithHostExist(req.params.accountName, res)) return | 27 | if (!await doesAccountNameWithHostExist(req.params.accountName, res)) return |
28 | 28 | ||
29 | return next() | 29 | return next() |
30 | } | 30 | } |
@@ -34,5 +34,5 @@ const accountsNameWithHostGetValidator = [ | |||
34 | 34 | ||
35 | export { | 35 | export { |
36 | localAccountValidator, | 36 | localAccountValidator, |
37 | accountsNameWithHostGetValidator | 37 | accountNameWithHostGetValidator |
38 | } | 38 | } |
diff --git a/server/middlewares/validators/activitypub/activity.ts b/server/middlewares/validators/activitypub/activity.ts index 3f9057c0c..7582f65e7 100644 --- a/server/middlewares/validators/activitypub/activity.ts +++ b/server/middlewares/validators/activitypub/activity.ts | |||
@@ -2,7 +2,6 @@ import * as express from 'express' | |||
2 | import { isRootActivityValid } from '../../../helpers/custom-validators/activitypub/activity' | 2 | import { isRootActivityValid } from '../../../helpers/custom-validators/activitypub/activity' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getServerActor } from '../../../helpers/utils' | 4 | import { getServerActor } from '../../../helpers/utils' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | ||
6 | 5 | ||
7 | async function activityPubValidator (req: express.Request, res: express.Response, next: express.NextFunction) { | 6 | async function activityPubValidator (req: express.Request, res: express.Response, next: express.NextFunction) { |
8 | logger.debug('Checking activity pub parameters') | 7 | logger.debug('Checking activity pub parameters') |
@@ -13,7 +12,7 @@ async function activityPubValidator (req: express.Request, res: express.Response | |||
13 | } | 12 | } |
14 | 13 | ||
15 | const serverActor = await getServerActor() | 14 | const serverActor = await getServerActor() |
16 | const remoteActor = res.locals.signature.actor as ActorModel | 15 | const remoteActor = res.locals.signature.actor |
17 | if (serverActor.id === remoteActor.id) { | 16 | if (serverActor.id === remoteActor.id) { |
18 | logger.error('Receiving request in INBOX by ourselves!', req.body) | 17 | logger.error('Receiving request in INBOX by ourselves!', req.body) |
19 | return res.status(409).end() | 18 | return res.status(409).end() |
diff --git a/server/middlewares/validators/avatar.ts b/server/middlewares/validators/avatar.ts index ddc14f531..bab3ed118 100644 --- a/server/middlewares/validators/avatar.ts +++ b/server/middlewares/validators/avatar.ts | |||
@@ -2,7 +2,7 @@ import * as express from 'express' | |||
2 | import { body } from 'express-validator/check' | 2 | import { body } from 'express-validator/check' |
3 | import { isAvatarFile } from '../../helpers/custom-validators/users' | 3 | import { isAvatarFile } from '../../helpers/custom-validators/users' |
4 | import { areValidationErrors } from './utils' | 4 | import { areValidationErrors } from './utils' |
5 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 5 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
6 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
7 | import { cleanUpReqFiles } from '../../helpers/express-utils' | 7 | import { cleanUpReqFiles } from '../../helpers/express-utils' |
8 | 8 | ||
diff --git a/server/middlewares/validators/blocklist.ts b/server/middlewares/validators/blocklist.ts index 109276c63..7c494de78 100644 --- a/server/middlewares/validators/blocklist.ts +++ b/server/middlewares/validators/blocklist.ts | |||
@@ -2,14 +2,13 @@ import { body, param } from 'express-validator/check' | |||
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { areValidationErrors } from './utils' | 4 | import { areValidationErrors } from './utils' |
5 | import { isAccountNameWithHostExist } from '../../helpers/custom-validators/accounts' | 5 | import { doesAccountNameWithHostExist } from '../../helpers/custom-validators/accounts' |
6 | import { UserModel } from '../../models/account/user' | ||
7 | import { AccountBlocklistModel } from '../../models/account/account-blocklist' | 6 | import { AccountBlocklistModel } from '../../models/account/account-blocklist' |
8 | import { isHostValid } from '../../helpers/custom-validators/servers' | 7 | import { isHostValid } from '../../helpers/custom-validators/servers' |
9 | import { ServerBlocklistModel } from '../../models/server/server-blocklist' | 8 | import { ServerBlocklistModel } from '../../models/server/server-blocklist' |
10 | import { ServerModel } from '../../models/server/server' | 9 | import { ServerModel } from '../../models/server/server' |
11 | import { CONFIG } from '../../initializers' | ||
12 | import { getServerActor } from '../../helpers/utils' | 10 | import { getServerActor } from '../../helpers/utils' |
11 | import { WEBSERVER } from '../../initializers/constants' | ||
13 | 12 | ||
14 | const blockAccountValidator = [ | 13 | const blockAccountValidator = [ |
15 | body('accountName').exists().withMessage('Should have an account name with host'), | 14 | body('accountName').exists().withMessage('Should have an account name with host'), |
@@ -18,9 +17,9 @@ const blockAccountValidator = [ | |||
18 | logger.debug('Checking blockAccountByAccountValidator parameters', { parameters: req.body }) | 17 | logger.debug('Checking blockAccountByAccountValidator parameters', { parameters: req.body }) |
19 | 18 | ||
20 | if (areValidationErrors(req, res)) return | 19 | if (areValidationErrors(req, res)) return |
21 | if (!await isAccountNameWithHostExist(req.body.accountName, res)) return | 20 | if (!await doesAccountNameWithHostExist(req.body.accountName, res)) return |
22 | 21 | ||
23 | const user = res.locals.oauth.token.User as UserModel | 22 | const user = res.locals.oauth.token.User |
24 | const accountToBlock = res.locals.account | 23 | const accountToBlock = res.locals.account |
25 | 24 | ||
26 | if (user.Account.id === accountToBlock.id) { | 25 | if (user.Account.id === accountToBlock.id) { |
@@ -42,11 +41,11 @@ const unblockAccountByAccountValidator = [ | |||
42 | logger.debug('Checking unblockAccountByAccountValidator parameters', { parameters: req.params }) | 41 | logger.debug('Checking unblockAccountByAccountValidator parameters', { parameters: req.params }) |
43 | 42 | ||
44 | if (areValidationErrors(req, res)) return | 43 | if (areValidationErrors(req, res)) return |
45 | if (!await isAccountNameWithHostExist(req.params.accountName, res)) return | 44 | if (!await doesAccountNameWithHostExist(req.params.accountName, res)) return |
46 | 45 | ||
47 | const user = res.locals.oauth.token.User as UserModel | 46 | const user = res.locals.oauth.token.User |
48 | const targetAccount = res.locals.account | 47 | const targetAccount = res.locals.account |
49 | if (!await isUnblockAccountExists(user.Account.id, targetAccount.id, res)) return | 48 | if (!await doesUnblockAccountExist(user.Account.id, targetAccount.id, res)) return |
50 | 49 | ||
51 | return next() | 50 | return next() |
52 | } | 51 | } |
@@ -59,11 +58,11 @@ const unblockAccountByServerValidator = [ | |||
59 | logger.debug('Checking unblockAccountByServerValidator parameters', { parameters: req.params }) | 58 | logger.debug('Checking unblockAccountByServerValidator parameters', { parameters: req.params }) |
60 | 59 | ||
61 | if (areValidationErrors(req, res)) return | 60 | if (areValidationErrors(req, res)) return |
62 | if (!await isAccountNameWithHostExist(req.params.accountName, res)) return | 61 | if (!await doesAccountNameWithHostExist(req.params.accountName, res)) return |
63 | 62 | ||
64 | const serverActor = await getServerActor() | 63 | const serverActor = await getServerActor() |
65 | const targetAccount = res.locals.account | 64 | const targetAccount = res.locals.account |
66 | if (!await isUnblockAccountExists(serverActor.Account.id, targetAccount.id, res)) return | 65 | if (!await doesUnblockAccountExist(serverActor.Account.id, targetAccount.id, res)) return |
67 | 66 | ||
68 | return next() | 67 | return next() |
69 | } | 68 | } |
@@ -79,7 +78,7 @@ const blockServerValidator = [ | |||
79 | 78 | ||
80 | const host: string = req.body.host | 79 | const host: string = req.body.host |
81 | 80 | ||
82 | if (host === CONFIG.WEBSERVER.HOST) { | 81 | if (host === WEBSERVER.HOST) { |
83 | return res.status(409) | 82 | return res.status(409) |
84 | .send({ error: 'You cannot block your own server.' }) | 83 | .send({ error: 'You cannot block your own server.' }) |
85 | .end() | 84 | .end() |
@@ -106,8 +105,8 @@ const unblockServerByAccountValidator = [ | |||
106 | 105 | ||
107 | if (areValidationErrors(req, res)) return | 106 | if (areValidationErrors(req, res)) return |
108 | 107 | ||
109 | const user = res.locals.oauth.token.User as UserModel | 108 | const user = res.locals.oauth.token.User |
110 | if (!await isUnblockServerExists(user.Account.id, req.params.host, res)) return | 109 | if (!await doesUnblockServerExist(user.Account.id, req.params.host, res)) return |
111 | 110 | ||
112 | return next() | 111 | return next() |
113 | } | 112 | } |
@@ -122,7 +121,7 @@ const unblockServerByServerValidator = [ | |||
122 | if (areValidationErrors(req, res)) return | 121 | if (areValidationErrors(req, res)) return |
123 | 122 | ||
124 | const serverActor = await getServerActor() | 123 | const serverActor = await getServerActor() |
125 | if (!await isUnblockServerExists(serverActor.Account.id, req.params.host, res)) return | 124 | if (!await doesUnblockServerExist(serverActor.Account.id, req.params.host, res)) return |
126 | 125 | ||
127 | return next() | 126 | return next() |
128 | } | 127 | } |
@@ -141,7 +140,7 @@ export { | |||
141 | 140 | ||
142 | // --------------------------------------------------------------------------- | 141 | // --------------------------------------------------------------------------- |
143 | 142 | ||
144 | async function isUnblockAccountExists (accountId: number, targetAccountId: number, res: express.Response) { | 143 | async function doesUnblockAccountExist (accountId: number, targetAccountId: number, res: express.Response) { |
145 | const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(accountId, targetAccountId) | 144 | const accountBlock = await AccountBlocklistModel.loadByAccountAndTarget(accountId, targetAccountId) |
146 | if (!accountBlock) { | 145 | if (!accountBlock) { |
147 | res.status(404) | 146 | res.status(404) |
@@ -156,7 +155,7 @@ async function isUnblockAccountExists (accountId: number, targetAccountId: numbe | |||
156 | return true | 155 | return true |
157 | } | 156 | } |
158 | 157 | ||
159 | async function isUnblockServerExists (accountId: number, host: string, res: express.Response) { | 158 | async function doesUnblockServerExist (accountId: number, host: string, res: express.Response) { |
160 | const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(accountId, host) | 159 | const serverBlock = await ServerBlocklistModel.loadByAccountAndHost(accountId, host) |
161 | if (!serverBlock) { | 160 | if (!serverBlock) { |
162 | res.status(404) | 161 | res.status(404) |
diff --git a/server/middlewares/validators/config.ts b/server/middlewares/validators/config.ts index 90108fa82..d015fa6fe 100644 --- a/server/middlewares/validators/config.ts +++ b/server/middlewares/validators/config.ts | |||
@@ -2,6 +2,8 @@ import * as express from 'express' | |||
2 | import { body } from 'express-validator/check' | 2 | import { body } from 'express-validator/check' |
3 | import { isUserNSFWPolicyValid, isUserVideoQuotaValid, isUserVideoQuotaDailyValid } from '../../helpers/custom-validators/users' | 3 | import { isUserNSFWPolicyValid, isUserVideoQuotaValid, isUserVideoQuotaDailyValid } from '../../helpers/custom-validators/users' |
4 | import { logger } from '../../helpers/logger' | 4 | import { logger } from '../../helpers/logger' |
5 | import { CustomConfig } from '../../../shared/models/server/custom-config.model' | ||
6 | import { Emailer } from '../../lib/emailer' | ||
5 | import { areValidationErrors } from './utils' | 7 | import { areValidationErrors } from './utils' |
6 | 8 | ||
7 | const customConfigUpdateValidator = [ | 9 | const customConfigUpdateValidator = [ |
@@ -42,15 +44,34 @@ const customConfigUpdateValidator = [ | |||
42 | body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'), | 44 | body('import.videos.http.enabled').isBoolean().withMessage('Should have a valid import video http enabled boolean'), |
43 | body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'), | 45 | body('import.videos.torrent.enabled').isBoolean().withMessage('Should have a valid import video torrent enabled boolean'), |
44 | 46 | ||
47 | body('followers.instance.enabled').isBoolean().withMessage('Should have a valid followers of instance boolean'), | ||
48 | body('followers.instance.manualApproval').isBoolean().withMessage('Should have a valid manual approval boolean'), | ||
49 | |||
45 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 50 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
46 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) | 51 | logger.debug('Checking customConfigUpdateValidator parameters', { parameters: req.body }) |
47 | 52 | ||
48 | if (areValidationErrors(req, res)) return | 53 | if (areValidationErrors(req, res)) return |
54 | if (!checkInvalidConfigIfEmailDisabled(req.body as CustomConfig, res)) return | ||
49 | 55 | ||
50 | return next() | 56 | return next() |
51 | } | 57 | } |
52 | ] | 58 | ] |
53 | 59 | ||
60 | // --------------------------------------------------------------------------- | ||
61 | |||
54 | export { | 62 | export { |
55 | customConfigUpdateValidator | 63 | customConfigUpdateValidator |
56 | } | 64 | } |
65 | |||
66 | function checkInvalidConfigIfEmailDisabled (customConfig: CustomConfig, res: express.Response) { | ||
67 | if (Emailer.isEnabled()) return true | ||
68 | |||
69 | if (customConfig.signup.requiresEmailVerification === true) { | ||
70 | res.status(400) | ||
71 | .send({ error: 'Emailer is disabled but you require signup email verification.' }) | ||
72 | .end() | ||
73 | return false | ||
74 | } | ||
75 | |||
76 | return true | ||
77 | } | ||
diff --git a/server/middlewares/validators/feeds.ts b/server/middlewares/validators/feeds.ts index 969ce2526..e4f5c98fe 100644 --- a/server/middlewares/validators/feeds.ts +++ b/server/middlewares/validators/feeds.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { param, query } from 'express-validator/check' | 2 | import { param, query } from 'express-validator/check' |
3 | import { isAccountIdExist, isAccountNameValid, isAccountNameWithHostExist } from '../../helpers/custom-validators/accounts' | 3 | import { doesAccountIdExist, isAccountNameValid, doesAccountNameWithHostExist } from '../../helpers/custom-validators/accounts' |
4 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { areValidationErrors } from './utils' | 6 | import { areValidationErrors } from './utils' |
7 | import { isValidRSSFeed } from '../../helpers/custom-validators/feeds' | 7 | import { isValidRSSFeed } from '../../helpers/custom-validators/feeds' |
8 | import { isVideoChannelIdExist, isVideoChannelNameWithHostExist } from '../../helpers/custom-validators/video-channels' | 8 | import { doesVideoChannelIdExist, doesVideoChannelNameWithHostExist } from '../../helpers/custom-validators/video-channels' |
9 | import { isVideoExist } from '../../helpers/custom-validators/videos' | 9 | import { doesVideoExist } from '../../helpers/custom-validators/videos' |
10 | import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' | 10 | import { isActorPreferredUsernameValid } from '../../helpers/custom-validators/activitypub/actor' |
11 | 11 | ||
12 | const videoFeedsValidator = [ | 12 | const videoFeedsValidator = [ |
@@ -22,10 +22,10 @@ const videoFeedsValidator = [ | |||
22 | 22 | ||
23 | if (areValidationErrors(req, res)) return | 23 | if (areValidationErrors(req, res)) return |
24 | 24 | ||
25 | if (req.query.accountId && !await isAccountIdExist(req.query.accountId, res)) return | 25 | if (req.query.accountId && !await doesAccountIdExist(req.query.accountId, res)) return |
26 | if (req.query.videoChannelId && !await isVideoChannelIdExist(req.query.videoChannelId, res)) return | 26 | if (req.query.videoChannelId && !await doesVideoChannelIdExist(req.query.videoChannelId, res)) return |
27 | if (req.query.accountName && !await isAccountNameWithHostExist(req.query.accountName, res)) return | 27 | if (req.query.accountName && !await doesAccountNameWithHostExist(req.query.accountName, res)) return |
28 | if (req.query.videoChannelName && !await isVideoChannelNameWithHostExist(req.query.videoChannelName, res)) return | 28 | if (req.query.videoChannelName && !await doesVideoChannelNameWithHostExist(req.query.videoChannelName, res)) return |
29 | 29 | ||
30 | return next() | 30 | return next() |
31 | } | 31 | } |
@@ -41,7 +41,7 @@ const videoCommentsFeedsValidator = [ | |||
41 | 41 | ||
42 | if (areValidationErrors(req, res)) return | 42 | if (areValidationErrors(req, res)) return |
43 | 43 | ||
44 | if (req.query.videoId && !await isVideoExist(req.query.videoId, res)) return | 44 | if (req.query.videoId && !await doesVideoExist(req.query.videoId, res)) return |
45 | 45 | ||
46 | return next() | 46 | return next() |
47 | } | 47 | } |
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index 73fa28be9..2e5a02307 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts | |||
@@ -4,16 +4,19 @@ import { isTestInstance } from '../../helpers/core-utils' | |||
4 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' | 4 | import { isEachUniqueHostValid, isHostValid } from '../../helpers/custom-validators/servers' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { getServerActor } from '../../helpers/utils' | 6 | import { getServerActor } from '../../helpers/utils' |
7 | import { CONFIG, SERVER_ACTOR_NAME } from '../../initializers' | 7 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' |
8 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 8 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
9 | import { areValidationErrors } from './utils' | 9 | import { areValidationErrors } from './utils' |
10 | import { ActorModel } from '../../models/activitypub/actor' | ||
11 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | ||
12 | import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | ||
10 | 13 | ||
11 | const followValidator = [ | 14 | const followValidator = [ |
12 | body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), | 15 | body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), |
13 | 16 | ||
14 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 17 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
15 | // Force https if the administrator wants to make friends | 18 | // Force https if the administrator wants to make friends |
16 | if (isTestInstance() === false && CONFIG.WEBSERVER.SCHEME === 'http') { | 19 | if (isTestInstance() === false && WEBSERVER.SCHEME === 'http') { |
17 | return res.status(500) | 20 | return res.status(500) |
18 | .json({ | 21 | .json({ |
19 | error: 'Cannot follow on a non HTTPS web server.' | 22 | error: 'Cannot follow on a non HTTPS web server.' |
@@ -33,7 +36,7 @@ const removeFollowingValidator = [ | |||
33 | param('host').custom(isHostValid).withMessage('Should have a valid host'), | 36 | param('host').custom(isHostValid).withMessage('Should have a valid host'), |
34 | 37 | ||
35 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 38 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
36 | logger.debug('Checking unfollow parameters', { parameters: req.params }) | 39 | logger.debug('Checking unfollowing parameters', { parameters: req.params }) |
37 | 40 | ||
38 | if (areValidationErrors(req, res)) return | 41 | if (areValidationErrors(req, res)) return |
39 | 42 | ||
@@ -44,7 +47,7 @@ const removeFollowingValidator = [ | |||
44 | return res | 47 | return res |
45 | .status(404) | 48 | .status(404) |
46 | .json({ | 49 | .json({ |
47 | error: `Follower ${req.params.host} not found.` | 50 | error: `Following ${req.params.host} not found.` |
48 | }) | 51 | }) |
49 | .end() | 52 | .end() |
50 | } | 53 | } |
@@ -54,9 +57,57 @@ const removeFollowingValidator = [ | |||
54 | } | 57 | } |
55 | ] | 58 | ] |
56 | 59 | ||
60 | const getFollowerValidator = [ | ||
61 | param('nameWithHost').custom(isValidActorHandle).withMessage('Should have a valid nameWithHost'), | ||
62 | |||
63 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
64 | logger.debug('Checking get follower parameters', { parameters: req.params }) | ||
65 | |||
66 | if (areValidationErrors(req, res)) return | ||
67 | |||
68 | let follow: ActorFollowModel | ||
69 | try { | ||
70 | const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost) | ||
71 | const actor = await ActorModel.loadByUrl(actorUrl) | ||
72 | |||
73 | const serverActor = await getServerActor() | ||
74 | follow = await ActorFollowModel.loadByActorAndTarget(actor.id, serverActor.id) | ||
75 | } catch (err) { | ||
76 | logger.warn('Cannot get actor from handle.', { handle: req.params.nameWithHost, err }) | ||
77 | } | ||
78 | |||
79 | if (!follow) { | ||
80 | return res | ||
81 | .status(404) | ||
82 | .json({ | ||
83 | error: `Follower ${req.params.nameWithHost} not found.` | ||
84 | }) | ||
85 | .end() | ||
86 | } | ||
87 | |||
88 | res.locals.follow = follow | ||
89 | return next() | ||
90 | } | ||
91 | ] | ||
92 | |||
93 | const acceptOrRejectFollowerValidator = [ | ||
94 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
95 | logger.debug('Checking accept/reject follower parameters', { parameters: req.params }) | ||
96 | |||
97 | const follow = res.locals.follow | ||
98 | if (follow.state !== 'pending') { | ||
99 | return res.status(400).json({ error: 'Follow is not in pending state.' }).end() | ||
100 | } | ||
101 | |||
102 | return next() | ||
103 | } | ||
104 | ] | ||
105 | |||
57 | // --------------------------------------------------------------------------- | 106 | // --------------------------------------------------------------------------- |
58 | 107 | ||
59 | export { | 108 | export { |
60 | followValidator, | 109 | followValidator, |
61 | removeFollowingValidator | 110 | removeFollowingValidator, |
111 | getFollowerValidator, | ||
112 | acceptOrRejectFollowerValidator | ||
62 | } | 113 | } |
diff --git a/server/middlewares/validators/logs.ts b/server/middlewares/validators/logs.ts new file mode 100644 index 000000000..7380c6edd --- /dev/null +++ b/server/middlewares/validators/logs.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import * as express from 'express' | ||
2 | import { logger } from '../../helpers/logger' | ||
3 | import { areValidationErrors } from './utils' | ||
4 | import { isDateValid } from '../../helpers/custom-validators/misc' | ||
5 | import { query } from 'express-validator/check' | ||
6 | import { isValidLogLevel } from '../../helpers/custom-validators/logs' | ||
7 | |||
8 | const getLogsValidator = [ | ||
9 | query('startDate') | ||
10 | .custom(isDateValid).withMessage('Should have a valid start date'), | ||
11 | query('level') | ||
12 | .optional() | ||
13 | .custom(isValidLogLevel).withMessage('Should have a valid level'), | ||
14 | query('endDate') | ||
15 | .optional() | ||
16 | .custom(isDateValid).withMessage('Should have a valid end date'), | ||
17 | |||
18 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
19 | logger.debug('Checking getLogsValidator parameters.', { parameters: req.query }) | ||
20 | |||
21 | if (areValidationErrors(req, res)) return | ||
22 | |||
23 | return next() | ||
24 | } | ||
25 | ] | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | export { | ||
30 | getLogsValidator | ||
31 | } | ||
diff --git a/server/middlewares/validators/oembed.ts b/server/middlewares/validators/oembed.ts index cd9b27b16..0bb908d0b 100644 --- a/server/middlewares/validators/oembed.ts +++ b/server/middlewares/validators/oembed.ts | |||
@@ -3,12 +3,12 @@ import { query } from 'express-validator/check' | |||
3 | import { join } from 'path' | 3 | import { join } from 'path' |
4 | import { isTestInstance } from '../../helpers/core-utils' | 4 | import { isTestInstance } from '../../helpers/core-utils' |
5 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | 5 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' |
6 | import { isVideoExist } from '../../helpers/custom-validators/videos' | 6 | import { doesVideoExist } from '../../helpers/custom-validators/videos' |
7 | import { logger } from '../../helpers/logger' | 7 | import { logger } from '../../helpers/logger' |
8 | import { CONFIG } from '../../initializers' | ||
9 | import { areValidationErrors } from './utils' | 8 | import { areValidationErrors } from './utils' |
9 | import { WEBSERVER } from '../../initializers/constants' | ||
10 | 10 | ||
11 | const urlShouldStartWith = CONFIG.WEBSERVER.SCHEME + '://' + join(CONFIG.WEBSERVER.HOST, 'videos', 'watch') + '/' | 11 | const urlShouldStartWith = WEBSERVER.SCHEME + '://' + join(WEBSERVER.HOST, 'videos', 'watch') + '/' |
12 | const videoWatchRegex = new RegExp('([^/]+)$') | 12 | const videoWatchRegex = new RegExp('([^/]+)$') |
13 | const isURLOptions = { | 13 | const isURLOptions = { |
14 | require_host: true, | 14 | require_host: true, |
@@ -52,7 +52,7 @@ const oembedValidator = [ | |||
52 | .end() | 52 | .end() |
53 | } | 53 | } |
54 | 54 | ||
55 | if (!await isVideoExist(videoId, res)) return | 55 | if (!await doesVideoExist(videoId, res)) return |
56 | 56 | ||
57 | return next() | 57 | return next() |
58 | } | 58 | } |
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts index c72ab78b2..76cf89c40 100644 --- a/server/middlewares/validators/redundancy.ts +++ b/server/middlewares/validators/redundancy.ts | |||
@@ -1,19 +1,15 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { param, body } from 'express-validator/check' | 3 | import { body, param } from 'express-validator/check' |
4 | import { exists, isBooleanValid, isIdOrUUIDValid, toIntOrNull } from '../../helpers/custom-validators/misc' | 4 | import { exists, isBooleanValid, isIdOrUUIDValid, toIntOrNull } from '../../helpers/custom-validators/misc' |
5 | import { isVideoExist } from '../../helpers/custom-validators/videos' | 5 | import { doesVideoExist } from '../../helpers/custom-validators/videos' |
6 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
7 | import { areValidationErrors } from './utils' | 7 | import { areValidationErrors } from './utils' |
8 | import { VideoModel } from '../../models/video/video' | ||
9 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 8 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
10 | import { isHostValid } from '../../helpers/custom-validators/servers' | 9 | import { isHostValid } from '../../helpers/custom-validators/servers' |
11 | import { getServerActor } from '../../helpers/utils' | ||
12 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | ||
13 | import { SERVER_ACTOR_NAME } from '../../initializers' | ||
14 | import { ServerModel } from '../../models/server/server' | 10 | import { ServerModel } from '../../models/server/server' |
15 | 11 | ||
16 | const videoRedundancyGetValidator = [ | 12 | const videoFileRedundancyGetValidator = [ |
17 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | 13 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), |
18 | param('resolution') | 14 | param('resolution') |
19 | .customSanitizer(toIntOrNull) | 15 | .customSanitizer(toIntOrNull) |
@@ -24,12 +20,12 @@ const videoRedundancyGetValidator = [ | |||
24 | .custom(exists).withMessage('Should have a valid fps'), | 20 | .custom(exists).withMessage('Should have a valid fps'), |
25 | 21 | ||
26 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 22 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
27 | logger.debug('Checking videoRedundancyGetValidator parameters', { parameters: req.params }) | 23 | logger.debug('Checking videoFileRedundancyGetValidator parameters', { parameters: req.params }) |
28 | 24 | ||
29 | if (areValidationErrors(req, res)) return | 25 | if (areValidationErrors(req, res)) return |
30 | if (!await isVideoExist(req.params.videoId, res)) return | 26 | if (!await doesVideoExist(req.params.videoId, res)) return |
31 | 27 | ||
32 | const video: VideoModel = res.locals.video | 28 | const video = res.locals.video |
33 | const videoFile = video.VideoFiles.find(f => { | 29 | const videoFile = video.VideoFiles.find(f => { |
34 | return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps) | 30 | return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps) |
35 | }) | 31 | }) |
@@ -38,7 +34,31 @@ const videoRedundancyGetValidator = [ | |||
38 | res.locals.videoFile = videoFile | 34 | res.locals.videoFile = videoFile |
39 | 35 | ||
40 | const videoRedundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id) | 36 | const videoRedundancy = await VideoRedundancyModel.loadLocalByFileId(videoFile.id) |
41 | if (!videoRedundancy)return res.status(404).json({ error: 'Video redundancy not found.' }) | 37 | if (!videoRedundancy) return res.status(404).json({ error: 'Video redundancy not found.' }) |
38 | res.locals.videoRedundancy = videoRedundancy | ||
39 | |||
40 | return next() | ||
41 | } | ||
42 | ] | ||
43 | |||
44 | const videoPlaylistRedundancyGetValidator = [ | ||
45 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid video id'), | ||
46 | param('streamingPlaylistType').custom(exists).withMessage('Should have a valid streaming playlist type'), | ||
47 | |||
48 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
49 | logger.debug('Checking videoPlaylistRedundancyGetValidator parameters', { parameters: req.params }) | ||
50 | |||
51 | if (areValidationErrors(req, res)) return | ||
52 | if (!await doesVideoExist(req.params.videoId, res)) return | ||
53 | |||
54 | const video = res.locals.video | ||
55 | const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType) | ||
56 | |||
57 | if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' }) | ||
58 | res.locals.videoStreamingPlaylist = videoStreamingPlaylist | ||
59 | |||
60 | const videoRedundancy = await VideoRedundancyModel.loadLocalByStreamingPlaylistId(videoStreamingPlaylist.id) | ||
61 | if (!videoRedundancy) return res.status(404).json({ error: 'Video redundancy not found.' }) | ||
42 | res.locals.videoRedundancy = videoRedundancy | 62 | res.locals.videoRedundancy = videoRedundancy |
43 | 63 | ||
44 | return next() | 64 | return next() |
@@ -75,6 +95,7 @@ const updateServerRedundancyValidator = [ | |||
75 | // --------------------------------------------------------------------------- | 95 | // --------------------------------------------------------------------------- |
76 | 96 | ||
77 | export { | 97 | export { |
78 | videoRedundancyGetValidator, | 98 | videoFileRedundancyGetValidator, |
99 | videoPlaylistRedundancyGetValidator, | ||
79 | updateServerRedundancyValidator | 100 | updateServerRedundancyValidator |
80 | } | 101 | } |
diff --git a/server/middlewares/validators/search.ts b/server/middlewares/validators/search.ts index 6a95d6095..7816d229c 100644 --- a/server/middlewares/validators/search.ts +++ b/server/middlewares/validators/search.ts | |||
@@ -10,6 +10,9 @@ const videosSearchValidator = [ | |||
10 | query('startDate').optional().custom(isDateValid).withMessage('Should have a valid start date'), | 10 | query('startDate').optional().custom(isDateValid).withMessage('Should have a valid start date'), |
11 | query('endDate').optional().custom(isDateValid).withMessage('Should have a valid end date'), | 11 | query('endDate').optional().custom(isDateValid).withMessage('Should have a valid end date'), |
12 | 12 | ||
13 | query('originallyPublishedStartDate').optional().custom(isDateValid).withMessage('Should have a valid published start date'), | ||
14 | query('originallyPublishedEndDate').optional().custom(isDateValid).withMessage('Should have a valid published end date'), | ||
15 | |||
13 | query('durationMin').optional().isInt().withMessage('Should have a valid min duration'), | 16 | query('durationMin').optional().isInt().withMessage('Should have a valid min duration'), |
14 | query('durationMax').optional().isInt().withMessage('Should have a valid max duration'), | 17 | query('durationMax').optional().isInt().withMessage('Should have a valid max duration'), |
15 | 18 | ||
diff --git a/server/middlewares/validators/server.ts b/server/middlewares/validators/server.ts index d85afc2ff..6eff8e9ee 100644 --- a/server/middlewares/validators/server.ts +++ b/server/middlewares/validators/server.ts | |||
@@ -7,7 +7,7 @@ import { body } from 'express-validator/check' | |||
7 | import { isUserDisplayNameValid } from '../../helpers/custom-validators/users' | 7 | import { isUserDisplayNameValid } from '../../helpers/custom-validators/users' |
8 | import { Emailer } from '../../lib/emailer' | 8 | import { Emailer } from '../../lib/emailer' |
9 | import { Redis } from '../../lib/redis' | 9 | import { Redis } from '../../lib/redis' |
10 | import { CONFIG } from '../../initializers/constants' | 10 | import { CONFIG } from '../../initializers/config' |
11 | 11 | ||
12 | const serverGetValidator = [ | 12 | const serverGetValidator = [ |
13 | body('host').custom(isHostValid).withMessage('Should have a valid host'), | 13 | body('host').custom(isHostValid).withMessage('Should have a valid host'), |
@@ -57,7 +57,7 @@ const contactAdministratorValidator = [ | |||
57 | .end() | 57 | .end() |
58 | } | 58 | } |
59 | 59 | ||
60 | if (await Redis.Instance.isContactFormIpExists(req.ip)) { | 60 | if (await Redis.Instance.doesContactFormIpExist(req.ip)) { |
61 | logger.info('Refusing a contact form by %s: already sent one recently.', req.ip) | 61 | logger.info('Refusing a contact form by %s: already sent one recently.', req.ip) |
62 | 62 | ||
63 | return res | 63 | return res |
diff --git a/server/middlewares/validators/sort.ts b/server/middlewares/validators/sort.ts index 5ceda845f..b497798d1 100644 --- a/server/middlewares/validators/sort.ts +++ b/server/middlewares/validators/sort.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { SORTABLE_COLUMNS } from '../../initializers' | 1 | import { SORTABLE_COLUMNS } from '../../initializers/constants' |
2 | import { checkSort, createSortableColumns } from './utils' | 2 | import { checkSort, createSortableColumns } from './utils' |
3 | 3 | ||
4 | // Initialize constants here for better performances | 4 | // Initialize constants here for better performances |
@@ -11,6 +11,7 @@ const SORTABLE_VIDEOS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VI | |||
11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) | 11 | const SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS_SEARCH) |
12 | const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS) | 12 | const SORTABLE_VIDEO_IMPORTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_IMPORTS) |
13 | const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) | 13 | const SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_COMMENT_THREADS) |
14 | const SORTABLE_VIDEO_RATES_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_RATES) | ||
14 | const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) | 15 | const SORTABLE_BLACKLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.BLACKLISTS) |
15 | const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS) | 16 | const SORTABLE_VIDEO_CHANNELS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_CHANNELS) |
16 | const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWERS) | 17 | const SORTABLE_FOLLOWERS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.FOLLOWERS) |
@@ -19,6 +20,7 @@ const SORTABLE_USER_SUBSCRIPTIONS_COLUMNS = createSortableColumns(SORTABLE_COLUM | |||
19 | const SORTABLE_ACCOUNTS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS_BLOCKLIST) | 20 | const SORTABLE_ACCOUNTS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.ACCOUNTS_BLOCKLIST) |
20 | const SORTABLE_SERVERS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.SERVERS_BLOCKLIST) | 21 | const SORTABLE_SERVERS_BLOCKLIST_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.SERVERS_BLOCKLIST) |
21 | const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USER_NOTIFICATIONS) | 22 | const SORTABLE_USER_NOTIFICATIONS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.USER_NOTIFICATIONS) |
23 | const SORTABLE_VIDEO_PLAYLISTS_COLUMNS = createSortableColumns(SORTABLE_COLUMNS.VIDEO_PLAYLISTS) | ||
22 | 24 | ||
23 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) | 25 | const usersSortValidator = checkSort(SORTABLE_USERS_COLUMNS) |
24 | const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) | 26 | const accountsSortValidator = checkSort(SORTABLE_ACCOUNTS_COLUMNS) |
@@ -29,6 +31,7 @@ const videoImportsSortValidator = checkSort(SORTABLE_VIDEO_IMPORTS_COLUMNS) | |||
29 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) | 31 | const videosSearchSortValidator = checkSort(SORTABLE_VIDEOS_SEARCH_COLUMNS) |
30 | const videoChannelsSearchSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS) | 32 | const videoChannelsSearchSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_SEARCH_COLUMNS) |
31 | const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS) | 33 | const videoCommentThreadsSortValidator = checkSort(SORTABLE_VIDEO_COMMENT_THREADS_COLUMNS) |
34 | const videoRatesSortValidator = checkSort(SORTABLE_VIDEO_RATES_COLUMNS) | ||
32 | const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS) | 35 | const blacklistSortValidator = checkSort(SORTABLE_BLACKLISTS_COLUMNS) |
33 | const videoChannelsSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_COLUMNS) | 36 | const videoChannelsSortValidator = checkSort(SORTABLE_VIDEO_CHANNELS_COLUMNS) |
34 | const followersSortValidator = checkSort(SORTABLE_FOLLOWERS_COLUMNS) | 37 | const followersSortValidator = checkSort(SORTABLE_FOLLOWERS_COLUMNS) |
@@ -37,6 +40,7 @@ const userSubscriptionsSortValidator = checkSort(SORTABLE_USER_SUBSCRIPTIONS_COL | |||
37 | const accountsBlocklistSortValidator = checkSort(SORTABLE_ACCOUNTS_BLOCKLIST_COLUMNS) | 40 | const accountsBlocklistSortValidator = checkSort(SORTABLE_ACCOUNTS_BLOCKLIST_COLUMNS) |
38 | const serversBlocklistSortValidator = checkSort(SORTABLE_SERVERS_BLOCKLIST_COLUMNS) | 41 | const serversBlocklistSortValidator = checkSort(SORTABLE_SERVERS_BLOCKLIST_COLUMNS) |
39 | const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COLUMNS) | 42 | const userNotificationsSortValidator = checkSort(SORTABLE_USER_NOTIFICATIONS_COLUMNS) |
43 | const videoPlaylistsSortValidator = checkSort(SORTABLE_VIDEO_PLAYLISTS_COLUMNS) | ||
40 | 44 | ||
41 | // --------------------------------------------------------------------------- | 45 | // --------------------------------------------------------------------------- |
42 | 46 | ||
@@ -53,9 +57,11 @@ export { | |||
53 | followingSortValidator, | 57 | followingSortValidator, |
54 | jobsSortValidator, | 58 | jobsSortValidator, |
55 | videoCommentThreadsSortValidator, | 59 | videoCommentThreadsSortValidator, |
60 | videoRatesSortValidator, | ||
56 | userSubscriptionsSortValidator, | 61 | userSubscriptionsSortValidator, |
57 | videoChannelsSearchSortValidator, | 62 | videoChannelsSearchSortValidator, |
58 | accountsBlocklistSortValidator, | 63 | accountsBlocklistSortValidator, |
59 | serversBlocklistSortValidator, | 64 | serversBlocklistSortValidator, |
60 | userNotificationsSortValidator | 65 | userNotificationsSortValidator, |
66 | videoPlaylistsSortValidator | ||
61 | } | 67 | } |
diff --git a/server/middlewares/validators/user-notifications.ts b/server/middlewares/validators/user-notifications.ts index 46486e081..3ded8d8cf 100644 --- a/server/middlewares/validators/user-notifications.ts +++ b/server/middlewares/validators/user-notifications.ts | |||
@@ -28,8 +28,22 @@ const updateNotificationSettingsValidator = [ | |||
28 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new comment on my video notification setting'), | 28 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new comment on my video notification setting'), |
29 | body('videoAbuseAsModerator') | 29 | body('videoAbuseAsModerator') |
30 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video abuse as moderator notification setting'), | 30 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new video abuse as moderator notification setting'), |
31 | body('videoAutoBlacklistAsModerator') | ||
32 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid video auto blacklist notification setting'), | ||
31 | body('blacklistOnMyVideo') | 33 | body('blacklistOnMyVideo') |
32 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new blacklist on my video notification setting'), | 34 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new blacklist on my video notification setting'), |
35 | body('myVideoImportFinished') | ||
36 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid video import finished video notification setting'), | ||
37 | body('myVideoPublished') | ||
38 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid video published notification setting'), | ||
39 | body('commentMention') | ||
40 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid comment mention notification setting'), | ||
41 | body('newFollow') | ||
42 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new follow notification setting'), | ||
43 | body('newUserRegistration') | ||
44 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new user registration notification setting'), | ||
45 | body('newInstanceFollower') | ||
46 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance follower notification setting'), | ||
33 | 47 | ||
34 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 48 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
35 | logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body }) | 49 | logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body }) |
diff --git a/server/middlewares/validators/user-subscriptions.ts b/server/middlewares/validators/user-subscriptions.ts index c5f8d9d4c..2356745d7 100644 --- a/server/middlewares/validators/user-subscriptions.ts +++ b/server/middlewares/validators/user-subscriptions.ts | |||
@@ -5,9 +5,8 @@ import { logger } from '../../helpers/logger' | |||
5 | import { areValidationErrors } from './utils' | 5 | import { areValidationErrors } from './utils' |
6 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 6 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
7 | import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | 7 | import { areValidActorHandles, isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' |
8 | import { UserModel } from '../../models/account/user' | ||
9 | import { CONFIG } from '../../initializers' | ||
10 | import { toArray } from '../../helpers/custom-validators/misc' | 8 | import { toArray } from '../../helpers/custom-validators/misc' |
9 | import { WEBSERVER } from '../../initializers/constants' | ||
11 | 10 | ||
12 | const userSubscriptionAddValidator = [ | 11 | const userSubscriptionAddValidator = [ |
13 | body('uri').custom(isValidActorHandle).withMessage('Should have a valid URI to follow (username@domain)'), | 12 | body('uri').custom(isValidActorHandle).withMessage('Should have a valid URI to follow (username@domain)'), |
@@ -44,9 +43,9 @@ const userSubscriptionGetValidator = [ | |||
44 | if (areValidationErrors(req, res)) return | 43 | if (areValidationErrors(req, res)) return |
45 | 44 | ||
46 | let [ name, host ] = req.params.uri.split('@') | 45 | let [ name, host ] = req.params.uri.split('@') |
47 | if (host === CONFIG.WEBSERVER.HOST) host = null | 46 | if (host === WEBSERVER.HOST) host = null |
48 | 47 | ||
49 | const user: UserModel = res.locals.oauth.token.User | 48 | const user = res.locals.oauth.token.User |
50 | const subscription = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(user.Account.Actor.id, name, host) | 49 | const subscription = await ActorFollowModel.loadByActorAndTargetNameAndHostForAPI(user.Account.Actor.id, name, host) |
51 | 50 | ||
52 | if (!subscription || !subscription.ActorFollowing.VideoChannel) { | 51 | if (!subscription || !subscription.ActorFollowing.VideoChannel) { |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 1bb0bfb1b..6d8cd7894 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -5,6 +5,7 @@ import { body, param } from 'express-validator/check' | |||
5 | import { omit } from 'lodash' | 5 | import { omit } from 'lodash' |
6 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' | 6 | import { isIdOrUUIDValid } from '../../helpers/custom-validators/misc' |
7 | import { | 7 | import { |
8 | isUserAdminFlagsValid, | ||
8 | isUserAutoPlayVideoValid, | 9 | isUserAutoPlayVideoValid, |
9 | isUserBlockedReasonValid, | 10 | isUserBlockedReasonValid, |
10 | isUserDescriptionValid, | 11 | isUserDescriptionValid, |
@@ -14,9 +15,10 @@ import { | |||
14 | isUserRoleValid, | 15 | isUserRoleValid, |
15 | isUserUsernameValid, | 16 | isUserUsernameValid, |
16 | isUserVideoQuotaDailyValid, | 17 | isUserVideoQuotaDailyValid, |
17 | isUserVideoQuotaValid, isUserVideosHistoryEnabledValid | 18 | isUserVideoQuotaValid, |
19 | isUserVideosHistoryEnabledValid | ||
18 | } from '../../helpers/custom-validators/users' | 20 | } from '../../helpers/custom-validators/users' |
19 | import { isVideoExist } from '../../helpers/custom-validators/videos' | 21 | import { doesVideoExist } from '../../helpers/custom-validators/videos' |
20 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
21 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' | 23 | import { isSignupAllowed, isSignupAllowedForCurrentIP } from '../../helpers/signup' |
22 | import { Redis } from '../../lib/redis' | 24 | import { Redis } from '../../lib/redis' |
@@ -31,6 +33,7 @@ const usersAddValidator = [ | |||
31 | body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), | 33 | body('videoQuota').custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), |
32 | body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'), | 34 | body('videoQuotaDaily').custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'), |
33 | body('role').custom(isUserRoleValid).withMessage('Should have a valid role'), | 35 | body('role').custom(isUserRoleValid).withMessage('Should have a valid role'), |
36 | body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'), | ||
34 | 37 | ||
35 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 38 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
36 | logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') }) | 39 | logger.debug('Checking usersAdd parameters', { parameters: omit(req.body, 'password') }) |
@@ -100,7 +103,7 @@ const usersBlockingValidator = [ | |||
100 | 103 | ||
101 | const deleteMeValidator = [ | 104 | const deleteMeValidator = [ |
102 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 105 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
103 | const user: UserModel = res.locals.oauth.token.User | 106 | const user = res.locals.oauth.token.User |
104 | if (user.username === 'root') { | 107 | if (user.username === 'root') { |
105 | return res.status(400) | 108 | return res.status(400) |
106 | .send({ error: 'You cannot delete your root account.' }) | 109 | .send({ error: 'You cannot delete your root account.' }) |
@@ -113,11 +116,13 @@ const deleteMeValidator = [ | |||
113 | 116 | ||
114 | const usersUpdateValidator = [ | 117 | const usersUpdateValidator = [ |
115 | param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), | 118 | param('id').isInt().not().isEmpty().withMessage('Should have a valid id'), |
119 | body('password').optional().custom(isUserPasswordValid).withMessage('Should have a valid password'), | ||
116 | body('email').optional().isEmail().withMessage('Should have a valid email attribute'), | 120 | body('email').optional().isEmail().withMessage('Should have a valid email attribute'), |
117 | body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'), | 121 | body('emailVerified').optional().isBoolean().withMessage('Should have a valid email verified attribute'), |
118 | body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), | 122 | body('videoQuota').optional().custom(isUserVideoQuotaValid).withMessage('Should have a valid user quota'), |
119 | body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'), | 123 | body('videoQuotaDaily').optional().custom(isUserVideoQuotaDailyValid).withMessage('Should have a valid daily user quota'), |
120 | body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'), | 124 | body('role').optional().custom(isUserRoleValid).withMessage('Should have a valid role'), |
125 | body('adminFlags').optional().custom(isUserAdminFlagsValid).withMessage('Should have a valid admin flags'), | ||
121 | 126 | ||
122 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 127 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
123 | logger.debug('Checking usersUpdate parameters', { parameters: req.body }) | 128 | logger.debug('Checking usersUpdate parameters', { parameters: req.body }) |
@@ -158,8 +163,7 @@ const usersUpdateMeValidator = [ | |||
158 | .end() | 163 | .end() |
159 | } | 164 | } |
160 | 165 | ||
161 | const user: UserModel = res.locals.oauth.token.User | 166 | const user = res.locals.oauth.token.User |
162 | |||
163 | if (await user.isPasswordMatch(req.body.currentPassword) !== true) { | 167 | if (await user.isPasswordMatch(req.body.currentPassword) !== true) { |
164 | return res.status(401) | 168 | return res.status(401) |
165 | .send({ error: 'currentPassword is invalid.' }) | 169 | .send({ error: 'currentPassword is invalid.' }) |
@@ -193,7 +197,7 @@ const usersVideoRatingValidator = [ | |||
193 | logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) | 197 | logger.debug('Checking usersVideoRating parameters', { parameters: req.params }) |
194 | 198 | ||
195 | if (areValidationErrors(req, res)) return | 199 | if (areValidationErrors(req, res)) return |
196 | if (!await isVideoExist(req.params.videoId, res, 'id')) return | 200 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return |
197 | 201 | ||
198 | return next() | 202 | return next() |
199 | } | 203 | } |
@@ -233,6 +237,7 @@ const usersAskResetPasswordValidator = [ | |||
233 | logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body }) | 237 | logger.debug('Checking usersAskResetPassword parameters', { parameters: req.body }) |
234 | 238 | ||
235 | if (areValidationErrors(req, res)) return | 239 | if (areValidationErrors(req, res)) return |
240 | |||
236 | const exists = await checkUserEmailExist(req.body.email, res, false) | 241 | const exists = await checkUserEmailExist(req.body.email, res, false) |
237 | if (!exists) { | 242 | if (!exists) { |
238 | logger.debug('User with email %s does not exist (asking reset password).', req.body.email) | 243 | logger.debug('User with email %s does not exist (asking reset password).', req.body.email) |
@@ -255,7 +260,7 @@ const usersResetPasswordValidator = [ | |||
255 | if (areValidationErrors(req, res)) return | 260 | if (areValidationErrors(req, res)) return |
256 | if (!await checkUserIdExist(req.params.id, res)) return | 261 | if (!await checkUserIdExist(req.params.id, res)) return |
257 | 262 | ||
258 | const user = res.locals.user as UserModel | 263 | const user = res.locals.user |
259 | const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id) | 264 | const redisVerificationString = await Redis.Instance.getResetPasswordLink(user.id) |
260 | 265 | ||
261 | if (redisVerificationString !== req.body.verificationString) { | 266 | if (redisVerificationString !== req.body.verificationString) { |
@@ -297,7 +302,7 @@ const usersVerifyEmailValidator = [ | |||
297 | if (areValidationErrors(req, res)) return | 302 | if (areValidationErrors(req, res)) return |
298 | if (!await checkUserIdExist(req.params.id, res)) return | 303 | if (!await checkUserIdExist(req.params.id, res)) return |
299 | 304 | ||
300 | const user = res.locals.user as UserModel | 305 | const user = res.locals.user |
301 | const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id) | 306 | const redisVerificationString = await Redis.Instance.getVerifyEmailLink(user.id) |
302 | 307 | ||
303 | if (redisVerificationString !== req.body.verificationString) { | 308 | if (redisVerificationString !== req.body.verificationString) { |
@@ -315,6 +320,20 @@ const userAutocompleteValidator = [ | |||
315 | param('search').isString().not().isEmpty().withMessage('Should have a search parameter') | 320 | param('search').isString().not().isEmpty().withMessage('Should have a search parameter') |
316 | ] | 321 | ] |
317 | 322 | ||
323 | const ensureAuthUserOwnsAccountValidator = [ | ||
324 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
325 | const user = res.locals.oauth.token.User | ||
326 | |||
327 | if (res.locals.account.id !== user.Account.id) { | ||
328 | return res.status(403) | ||
329 | .send({ error: 'Only owner can access ratings list.' }) | ||
330 | .end() | ||
331 | } | ||
332 | |||
333 | return next() | ||
334 | } | ||
335 | ] | ||
336 | |||
318 | // --------------------------------------------------------------------------- | 337 | // --------------------------------------------------------------------------- |
319 | 338 | ||
320 | export { | 339 | export { |
@@ -333,7 +352,8 @@ export { | |||
333 | usersResetPasswordValidator, | 352 | usersResetPasswordValidator, |
334 | usersAskSendVerifyEmailValidator, | 353 | usersAskSendVerifyEmailValidator, |
335 | usersVerifyEmailValidator, | 354 | usersVerifyEmailValidator, |
336 | userAutocompleteValidator | 355 | userAutocompleteValidator, |
356 | ensureAuthUserOwnsAccountValidator | ||
337 | } | 357 | } |
338 | 358 | ||
339 | // --------------------------------------------------------------------------- | 359 | // --------------------------------------------------------------------------- |
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts index be26ca16a..d1910a992 100644 --- a/server/middlewares/validators/videos/video-abuses.ts +++ b/server/middlewares/validators/videos/video-abuses.ts | |||
@@ -2,11 +2,11 @@ import * as express from 'express' | |||
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { body, param } from 'express-validator/check' | 3 | import { body, param } from 'express-validator/check' |
4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' |
5 | import { isVideoExist } from '../../../helpers/custom-validators/videos' | 5 | import { doesVideoExist } from '../../../helpers/custom-validators/videos' |
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { areValidationErrors } from '../utils' | 7 | import { areValidationErrors } from '../utils' |
8 | import { | 8 | import { |
9 | isVideoAbuseExist, | 9 | doesVideoAbuseExist, |
10 | isVideoAbuseModerationCommentValid, | 10 | isVideoAbuseModerationCommentValid, |
11 | isVideoAbuseReasonValid, | 11 | isVideoAbuseReasonValid, |
12 | isVideoAbuseStateValid | 12 | isVideoAbuseStateValid |
@@ -20,7 +20,7 @@ const videoAbuseReportValidator = [ | |||
20 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | 20 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) |
21 | 21 | ||
22 | if (areValidationErrors(req, res)) return | 22 | if (areValidationErrors(req, res)) return |
23 | if (!await isVideoExist(req.params.videoId, res)) return | 23 | if (!await doesVideoExist(req.params.videoId, res)) return |
24 | 24 | ||
25 | return next() | 25 | return next() |
26 | } | 26 | } |
@@ -34,8 +34,8 @@ const videoAbuseGetValidator = [ | |||
34 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) | 34 | logger.debug('Checking videoAbuseGetValidator parameters', { parameters: req.body }) |
35 | 35 | ||
36 | if (areValidationErrors(req, res)) return | 36 | if (areValidationErrors(req, res)) return |
37 | if (!await isVideoExist(req.params.videoId, res)) return | 37 | if (!await doesVideoExist(req.params.videoId, res)) return |
38 | if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return | 38 | if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return |
39 | 39 | ||
40 | return next() | 40 | return next() |
41 | } | 41 | } |
@@ -55,8 +55,8 @@ const videoAbuseUpdateValidator = [ | |||
55 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) | 55 | logger.debug('Checking videoAbuseUpdateValidator parameters', { parameters: req.body }) |
56 | 56 | ||
57 | if (areValidationErrors(req, res)) return | 57 | if (areValidationErrors(req, res)) return |
58 | if (!await isVideoExist(req.params.videoId, res)) return | 58 | if (!await doesVideoExist(req.params.videoId, res)) return |
59 | if (!await isVideoAbuseExist(req.params.id, res.locals.video.id, res)) return | 59 | if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return |
60 | 60 | ||
61 | return next() | 61 | return next() |
62 | } | 62 | } |
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts index 2688f63ae..1d7ddb2e3 100644 --- a/server/middlewares/validators/videos/video-blacklist.ts +++ b/server/middlewares/validators/videos/video-blacklist.ts | |||
@@ -1,11 +1,14 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator/check' | 2 | import { body, param, query } from 'express-validator/check' |
3 | import { isBooleanValid, isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' | 3 | import { isBooleanValid, isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' |
4 | import { isVideoExist } from '../../../helpers/custom-validators/videos' | 4 | import { doesVideoExist } from '../../../helpers/custom-validators/videos' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { areValidationErrors } from '../utils' | 6 | import { areValidationErrors } from '../utils' |
7 | import { isVideoBlacklistExist, isVideoBlacklistReasonValid } from '../../../helpers/custom-validators/video-blacklist' | 7 | import { |
8 | import { VideoModel } from '../../../models/video/video' | 8 | doesVideoBlacklistExist, |
9 | isVideoBlacklistReasonValid, | ||
10 | isVideoBlacklistTypeValid | ||
11 | } from '../../../helpers/custom-validators/video-blacklist' | ||
9 | 12 | ||
10 | const videosBlacklistRemoveValidator = [ | 13 | const videosBlacklistRemoveValidator = [ |
11 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 14 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), |
@@ -14,8 +17,8 @@ const videosBlacklistRemoveValidator = [ | |||
14 | logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) | 17 | logger.debug('Checking blacklistRemove parameters.', { parameters: req.params }) |
15 | 18 | ||
16 | if (areValidationErrors(req, res)) return | 19 | if (areValidationErrors(req, res)) return |
17 | if (!await isVideoExist(req.params.videoId, res)) return | 20 | if (!await doesVideoExist(req.params.videoId, res)) return |
18 | if (!await isVideoBlacklistExist(res.locals.video.id, res)) return | 21 | if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return |
19 | 22 | ||
20 | return next() | 23 | return next() |
21 | } | 24 | } |
@@ -35,9 +38,9 @@ const videosBlacklistAddValidator = [ | |||
35 | logger.debug('Checking videosBlacklistAdd parameters', { parameters: req.params }) | 38 | logger.debug('Checking videosBlacklistAdd parameters', { parameters: req.params }) |
36 | 39 | ||
37 | if (areValidationErrors(req, res)) return | 40 | if (areValidationErrors(req, res)) return |
38 | if (!await isVideoExist(req.params.videoId, res)) return | 41 | if (!await doesVideoExist(req.params.videoId, res)) return |
39 | 42 | ||
40 | const video: VideoModel = res.locals.video | 43 | const video = res.locals.video |
41 | if (req.body.unfederate === true && video.remote === true) { | 44 | if (req.body.unfederate === true && video.remote === true) { |
42 | return res | 45 | return res |
43 | .status(409) | 46 | .status(409) |
@@ -59,8 +62,22 @@ const videosBlacklistUpdateValidator = [ | |||
59 | logger.debug('Checking videosBlacklistUpdate parameters', { parameters: req.params }) | 62 | logger.debug('Checking videosBlacklistUpdate parameters', { parameters: req.params }) |
60 | 63 | ||
61 | if (areValidationErrors(req, res)) return | 64 | if (areValidationErrors(req, res)) return |
62 | if (!await isVideoExist(req.params.videoId, res)) return | 65 | if (!await doesVideoExist(req.params.videoId, res)) return |
63 | if (!await isVideoBlacklistExist(res.locals.video.id, res)) return | 66 | if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return |
67 | |||
68 | return next() | ||
69 | } | ||
70 | ] | ||
71 | |||
72 | const videosBlacklistFiltersValidator = [ | ||
73 | query('type') | ||
74 | .optional() | ||
75 | .custom(isVideoBlacklistTypeValid).withMessage('Should have a valid video blacklist type attribute'), | ||
76 | |||
77 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
78 | logger.debug('Checking videos blacklist filters query', { parameters: req.query }) | ||
79 | |||
80 | if (areValidationErrors(req, res)) return | ||
64 | 81 | ||
65 | return next() | 82 | return next() |
66 | } | 83 | } |
@@ -71,5 +88,6 @@ const videosBlacklistUpdateValidator = [ | |||
71 | export { | 88 | export { |
72 | videosBlacklistAddValidator, | 89 | videosBlacklistAddValidator, |
73 | videosBlacklistRemoveValidator, | 90 | videosBlacklistRemoveValidator, |
74 | videosBlacklistUpdateValidator | 91 | videosBlacklistUpdateValidator, |
92 | videosBlacklistFiltersValidator | ||
75 | } | 93 | } |
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts index 63d84fbec..d857ac3ec 100644 --- a/server/middlewares/validators/videos/video-captions.ts +++ b/server/middlewares/validators/videos/video-captions.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { areValidationErrors } from '../utils' | 2 | import { areValidationErrors } from '../utils' |
3 | import { checkUserCanManageVideo, isVideoExist } from '../../../helpers/custom-validators/videos' | 3 | import { checkUserCanManageVideo, doesVideoExist } from '../../../helpers/custom-validators/videos' |
4 | import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' |
5 | import { body, param } from 'express-validator/check' | 5 | import { body, param } from 'express-validator/check' |
6 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 6 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
7 | import { UserRight } from '../../../../shared' | 7 | import { UserRight } from '../../../../shared' |
8 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
9 | import { isVideoCaptionExist, isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' | 9 | import { doesVideoCaptionExist, isVideoCaptionFile, isVideoCaptionLanguageValid } from '../../../helpers/custom-validators/video-captions' |
10 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 10 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
11 | 11 | ||
12 | const addVideoCaptionValidator = [ | 12 | const addVideoCaptionValidator = [ |
@@ -22,7 +22,7 @@ const addVideoCaptionValidator = [ | |||
22 | logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) | 22 | logger.debug('Checking addVideoCaption parameters', { parameters: req.body }) |
23 | 23 | ||
24 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) | 24 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) |
25 | if (!await isVideoExist(req.params.videoId, res)) return cleanUpReqFiles(req) | 25 | if (!await doesVideoExist(req.params.videoId, res)) return cleanUpReqFiles(req) |
26 | 26 | ||
27 | // Check if the user who did the request is able to update the video | 27 | // Check if the user who did the request is able to update the video |
28 | const user = res.locals.oauth.token.User | 28 | const user = res.locals.oauth.token.User |
@@ -40,8 +40,8 @@ const deleteVideoCaptionValidator = [ | |||
40 | logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params }) | 40 | logger.debug('Checking deleteVideoCaption parameters', { parameters: req.params }) |
41 | 41 | ||
42 | if (areValidationErrors(req, res)) return | 42 | if (areValidationErrors(req, res)) return |
43 | if (!await isVideoExist(req.params.videoId, res)) return | 43 | if (!await doesVideoExist(req.params.videoId, res)) return |
44 | if (!await isVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return | 44 | if (!await doesVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return |
45 | 45 | ||
46 | // Check if the user who did the request is able to update the video | 46 | // Check if the user who did the request is able to update the video |
47 | const user = res.locals.oauth.token.User | 47 | const user = res.locals.oauth.token.User |
@@ -58,7 +58,7 @@ const listVideoCaptionsValidator = [ | |||
58 | logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) | 58 | logger.debug('Checking listVideoCaptions parameters', { parameters: req.params }) |
59 | 59 | ||
60 | if (areValidationErrors(req, res)) return | 60 | if (areValidationErrors(req, res)) return |
61 | if (!await isVideoExist(req.params.videoId, res, 'id')) return | 61 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return |
62 | 62 | ||
63 | return next() | 63 | return next() |
64 | } | 64 | } |
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index f039794e0..4b26f0bc4 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { body, param } from 'express-validator/check' | 2 | import { body, param } from 'express-validator/check' |
3 | import { UserRight } from '../../../../shared' | 3 | import { UserRight } from '../../../../shared' |
4 | import { isAccountNameWithHostExist } from '../../../helpers/custom-validators/accounts' | ||
5 | import { | 4 | import { |
6 | isLocalVideoChannelNameExist, | 5 | doesLocalVideoChannelNameExist, |
6 | doesVideoChannelNameWithHostExist, | ||
7 | isVideoChannelDescriptionValid, | 7 | isVideoChannelDescriptionValid, |
8 | isVideoChannelNameValid, | 8 | isVideoChannelNameValid, |
9 | isVideoChannelNameWithHostExist, | ||
10 | isVideoChannelSupportValid | 9 | isVideoChannelSupportValid |
11 | } from '../../../helpers/custom-validators/video-channels' | 10 | } from '../../../helpers/custom-validators/video-channels' |
12 | import { logger } from '../../../helpers/logger' | 11 | import { logger } from '../../../helpers/logger' |
@@ -16,19 +15,6 @@ import { areValidationErrors } from '../utils' | |||
16 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' | 15 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' |
17 | import { ActorModel } from '../../../models/activitypub/actor' | 16 | import { ActorModel } from '../../../models/activitypub/actor' |
18 | 17 | ||
19 | const listVideoAccountChannelsValidator = [ | ||
20 | param('accountName').exists().withMessage('Should have a valid account name'), | ||
21 | |||
22 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
23 | logger.debug('Checking listVideoAccountChannelsValidator parameters', { parameters: req.body }) | ||
24 | |||
25 | if (areValidationErrors(req, res)) return | ||
26 | if (!await isAccountNameWithHostExist(req.params.accountName, res)) return | ||
27 | |||
28 | return next() | ||
29 | } | ||
30 | ] | ||
31 | |||
32 | const videoChannelsAddValidator = [ | 18 | const videoChannelsAddValidator = [ |
33 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), | 19 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), |
34 | body('displayName').custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), | 20 | body('displayName').custom(isVideoChannelNameValid).withMessage('Should have a valid display name'), |
@@ -62,7 +48,7 @@ const videoChannelsUpdateValidator = [ | |||
62 | logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) | 48 | logger.debug('Checking videoChannelsUpdate parameters', { parameters: req.body }) |
63 | 49 | ||
64 | if (areValidationErrors(req, res)) return | 50 | if (areValidationErrors(req, res)) return |
65 | if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return | 51 | if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return |
66 | 52 | ||
67 | // We need to make additional checks | 53 | // We need to make additional checks |
68 | if (res.locals.videoChannel.Actor.isOwned() === false) { | 54 | if (res.locals.videoChannel.Actor.isOwned() === false) { |
@@ -88,7 +74,7 @@ const videoChannelsRemoveValidator = [ | |||
88 | logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) | 74 | logger.debug('Checking videoChannelsRemove parameters', { parameters: req.params }) |
89 | 75 | ||
90 | if (areValidationErrors(req, res)) return | 76 | if (areValidationErrors(req, res)) return |
91 | if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return | 77 | if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return |
92 | 78 | ||
93 | if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return | 79 | if (!checkUserCanDeleteVideoChannel(res.locals.oauth.token.User, res.locals.videoChannel, res)) return |
94 | if (!await checkVideoChannelIsNotTheLastOne(res)) return | 80 | if (!await checkVideoChannelIsNotTheLastOne(res)) return |
@@ -105,7 +91,7 @@ const videoChannelsNameWithHostValidator = [ | |||
105 | 91 | ||
106 | if (areValidationErrors(req, res)) return | 92 | if (areValidationErrors(req, res)) return |
107 | 93 | ||
108 | if (!await isVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return | 94 | if (!await doesVideoChannelNameWithHostExist(req.params.nameWithHost, res)) return |
109 | 95 | ||
110 | return next() | 96 | return next() |
111 | } | 97 | } |
@@ -118,7 +104,7 @@ const localVideoChannelValidator = [ | |||
118 | logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params }) | 104 | logger.debug('Checking localVideoChannelValidator parameters', { parameters: req.params }) |
119 | 105 | ||
120 | if (areValidationErrors(req, res)) return | 106 | if (areValidationErrors(req, res)) return |
121 | if (!await isLocalVideoChannelNameExist(req.params.name, res)) return | 107 | if (!await doesLocalVideoChannelNameExist(req.params.name, res)) return |
122 | 108 | ||
123 | return next() | 109 | return next() |
124 | } | 110 | } |
@@ -127,7 +113,6 @@ const localVideoChannelValidator = [ | |||
127 | // --------------------------------------------------------------------------- | 113 | // --------------------------------------------------------------------------- |
128 | 114 | ||
129 | export { | 115 | export { |
130 | listVideoAccountChannelsValidator, | ||
131 | videoChannelsAddValidator, | 116 | videoChannelsAddValidator, |
132 | videoChannelsUpdateValidator, | 117 | videoChannelsUpdateValidator, |
133 | videoChannelsRemoveValidator, | 118 | videoChannelsRemoveValidator, |
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts index 348d33082..ffde208b7 100644 --- a/server/middlewares/validators/videos/video-comments.ts +++ b/server/middlewares/validators/videos/video-comments.ts | |||
@@ -3,7 +3,7 @@ import { body, param } from 'express-validator/check' | |||
3 | import { UserRight } from '../../../../shared' | 3 | import { UserRight } from '../../../../shared' |
4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' |
5 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' | 5 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' |
6 | import { isVideoExist } from '../../../helpers/custom-validators/videos' | 6 | import { doesVideoExist } from '../../../helpers/custom-validators/videos' |
7 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { UserModel } from '../../../models/account/user' | 8 | import { UserModel } from '../../../models/account/user' |
9 | import { VideoModel } from '../../../models/video/video' | 9 | import { VideoModel } from '../../../models/video/video' |
@@ -17,7 +17,7 @@ const listVideoCommentThreadsValidator = [ | |||
17 | logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params }) | 17 | logger.debug('Checking listVideoCommentThreads parameters.', { parameters: req.params }) |
18 | 18 | ||
19 | if (areValidationErrors(req, res)) return | 19 | if (areValidationErrors(req, res)) return |
20 | if (!await isVideoExist(req.params.videoId, res, 'only-video')) return | 20 | if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return |
21 | 21 | ||
22 | return next() | 22 | return next() |
23 | } | 23 | } |
@@ -31,8 +31,8 @@ const listVideoThreadCommentsValidator = [ | |||
31 | logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params }) | 31 | logger.debug('Checking listVideoThreadComments parameters.', { parameters: req.params }) |
32 | 32 | ||
33 | if (areValidationErrors(req, res)) return | 33 | if (areValidationErrors(req, res)) return |
34 | if (!await isVideoExist(req.params.videoId, res, 'only-video')) return | 34 | if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return |
35 | if (!await isVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return | 35 | if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return |
36 | 36 | ||
37 | return next() | 37 | return next() |
38 | } | 38 | } |
@@ -46,7 +46,7 @@ const addVideoCommentThreadValidator = [ | |||
46 | logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body }) | 46 | logger.debug('Checking addVideoCommentThread parameters.', { parameters: req.params, body: req.body }) |
47 | 47 | ||
48 | if (areValidationErrors(req, res)) return | 48 | if (areValidationErrors(req, res)) return |
49 | if (!await isVideoExist(req.params.videoId, res)) return | 49 | if (!await doesVideoExist(req.params.videoId, res)) return |
50 | if (!isVideoCommentsEnabled(res.locals.video, res)) return | 50 | if (!isVideoCommentsEnabled(res.locals.video, res)) return |
51 | 51 | ||
52 | return next() | 52 | return next() |
@@ -62,9 +62,9 @@ const addVideoCommentReplyValidator = [ | |||
62 | logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body }) | 62 | logger.debug('Checking addVideoCommentReply parameters.', { parameters: req.params, body: req.body }) |
63 | 63 | ||
64 | if (areValidationErrors(req, res)) return | 64 | if (areValidationErrors(req, res)) return |
65 | if (!await isVideoExist(req.params.videoId, res)) return | 65 | if (!await doesVideoExist(req.params.videoId, res)) return |
66 | if (!isVideoCommentsEnabled(res.locals.video, res)) return | 66 | if (!isVideoCommentsEnabled(res.locals.video, res)) return |
67 | if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 67 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return |
68 | 68 | ||
69 | return next() | 69 | return next() |
70 | } | 70 | } |
@@ -78,8 +78,8 @@ const videoCommentGetValidator = [ | |||
78 | logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params }) | 78 | logger.debug('Checking videoCommentGetValidator parameters.', { parameters: req.params }) |
79 | 79 | ||
80 | if (areValidationErrors(req, res)) return | 80 | if (areValidationErrors(req, res)) return |
81 | if (!await isVideoExist(req.params.videoId, res, 'id')) return | 81 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return |
82 | if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 82 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return |
83 | 83 | ||
84 | return next() | 84 | return next() |
85 | } | 85 | } |
@@ -93,8 +93,8 @@ const removeVideoCommentValidator = [ | |||
93 | logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params }) | 93 | logger.debug('Checking removeVideoCommentValidator parameters.', { parameters: req.params }) |
94 | 94 | ||
95 | if (areValidationErrors(req, res)) return | 95 | if (areValidationErrors(req, res)) return |
96 | if (!await isVideoExist(req.params.videoId, res)) return | 96 | if (!await doesVideoExist(req.params.videoId, res)) return |
97 | if (!await isVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 97 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return |
98 | 98 | ||
99 | // Check if the user who did the request is able to delete the video | 99 | // Check if the user who did the request is able to delete the video |
100 | if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return | 100 | if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return |
@@ -116,7 +116,7 @@ export { | |||
116 | 116 | ||
117 | // --------------------------------------------------------------------------- | 117 | // --------------------------------------------------------------------------- |
118 | 118 | ||
119 | async function isVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) { | 119 | async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) { |
120 | const videoComment = await VideoCommentModel.loadById(id) | 120 | const videoComment = await VideoCommentModel.loadById(id) |
121 | 121 | ||
122 | if (!videoComment) { | 122 | if (!videoComment) { |
@@ -147,7 +147,7 @@ async function isVideoCommentThreadExist (id: number, video: VideoModel, res: ex | |||
147 | return true | 147 | return true |
148 | } | 148 | } |
149 | 149 | ||
150 | async function isVideoCommentExist (id: number, video: VideoModel, res: express.Response) { | 150 | async function doesVideoCommentExist (id: number, video: VideoModel, res: express.Response) { |
151 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) | 151 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) |
152 | 152 | ||
153 | if (!videoComment) { | 153 | if (!videoComment) { |
diff --git a/server/middlewares/validators/videos/video-imports.ts b/server/middlewares/validators/videos/video-imports.ts index 48d20f904..452084a7c 100644 --- a/server/middlewares/validators/videos/video-imports.ts +++ b/server/middlewares/validators/videos/video-imports.ts | |||
@@ -3,14 +3,14 @@ import { body } from 'express-validator/check' | |||
3 | import { isIdValid } from '../../../helpers/custom-validators/misc' | 3 | import { isIdValid } from '../../../helpers/custom-validators/misc' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { areValidationErrors } from '../utils' | 5 | import { areValidationErrors } from '../utils' |
6 | import { getCommonVideoAttributes } from './videos' | 6 | import { getCommonVideoEditAttributes } from './videos' |
7 | import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' | 7 | import { isVideoImportTargetUrlValid, isVideoImportTorrentFile } from '../../../helpers/custom-validators/video-imports' |
8 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 8 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
9 | import { isVideoChannelOfAccountExist, isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos' | 9 | import { doesVideoChannelOfAccountExist, isVideoMagnetUriValid, isVideoNameValid } from '../../../helpers/custom-validators/videos' |
10 | import { CONFIG } from '../../../initializers/constants' | 10 | import { CONFIG } from '../../../initializers/config' |
11 | import { CONSTRAINTS_FIELDS } from '../../../initializers' | 11 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
12 | 12 | ||
13 | const videoImportAddValidator = getCommonVideoAttributes().concat([ | 13 | const videoImportAddValidator = getCommonVideoEditAttributes().concat([ |
14 | body('channelId') | 14 | body('channelId') |
15 | .toInt() | 15 | .toInt() |
16 | .custom(isIdValid).withMessage('Should have correct video channel id'), | 16 | .custom(isIdValid).withMessage('Should have correct video channel id'), |
@@ -51,7 +51,7 @@ const videoImportAddValidator = getCommonVideoAttributes().concat([ | |||
51 | .end() | 51 | .end() |
52 | } | 52 | } |
53 | 53 | ||
54 | if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | 54 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) |
55 | 55 | ||
56 | // Check we have at least 1 required param | 56 | // Check we have at least 1 required param |
57 | if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) { | 57 | if (!req.body.targetUrl && !req.body.magnetUri && !torrentFile) { |
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts new file mode 100644 index 000000000..2c3f7e542 --- /dev/null +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -0,0 +1,408 @@ | |||
1 | import * as express from 'express' | ||
2 | import { body, param, query, ValidationChain } from 'express-validator/check' | ||
3 | import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' | ||
4 | import { logger } from '../../../helpers/logger' | ||
5 | import { UserModel } from '../../../models/account/user' | ||
6 | import { areValidationErrors } from '../utils' | ||
7 | import { doesVideoExist, isVideoImage } from '../../../helpers/custom-validators/videos' | ||
8 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | ||
9 | import { isArrayOf, isIdOrUUIDValid, isIdValid, isUUIDValid, toIntArray, toValueOrNull } from '../../../helpers/custom-validators/misc' | ||
10 | import { | ||
11 | doesVideoPlaylistExist, | ||
12 | isVideoPlaylistDescriptionValid, | ||
13 | isVideoPlaylistNameValid, | ||
14 | isVideoPlaylistPrivacyValid, | ||
15 | isVideoPlaylistTimestampValid, | ||
16 | isVideoPlaylistTypeValid | ||
17 | } from '../../../helpers/custom-validators/video-playlists' | ||
18 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
19 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | ||
20 | import { doesVideoChannelIdExist } from '../../../helpers/custom-validators/video-channels' | ||
21 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' | ||
22 | import { authenticatePromiseIfNeeded } from '../../oauth' | ||
23 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
24 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' | ||
25 | |||
26 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ | ||
27 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
28 | logger.debug('Checking videoPlaylistsAddValidator parameters', { parameters: req.body }) | ||
29 | |||
30 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) | ||
31 | |||
32 | const body: VideoPlaylistCreate = req.body | ||
33 | if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) | ||
34 | |||
35 | if (body.privacy === VideoPlaylistPrivacy.PUBLIC && !body.videoChannelId) { | ||
36 | cleanUpReqFiles(req) | ||
37 | return res.status(400) | ||
38 | .json({ error: 'Cannot set "public" a playlist that is not assigned to a channel.' }) | ||
39 | } | ||
40 | |||
41 | return next() | ||
42 | } | ||
43 | ]) | ||
44 | |||
45 | const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ | ||
46 | param('playlistId') | ||
47 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
48 | |||
49 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
50 | logger.debug('Checking videoPlaylistsUpdateValidator parameters', { parameters: req.body }) | ||
51 | |||
52 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) | ||
53 | |||
54 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req) | ||
55 | |||
56 | const videoPlaylist = res.locals.videoPlaylist | ||
57 | |||
58 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { | ||
59 | return cleanUpReqFiles(req) | ||
60 | } | ||
61 | |||
62 | const body: VideoPlaylistUpdate = req.body | ||
63 | |||
64 | if (videoPlaylist.privacy !== VideoPlaylistPrivacy.PRIVATE && body.privacy === VideoPlaylistPrivacy.PRIVATE) { | ||
65 | cleanUpReqFiles(req) | ||
66 | return res.status(400) | ||
67 | .json({ error: 'Cannot set "private" a video playlist that was not private.' }) | ||
68 | } | ||
69 | |||
70 | const newPrivacy = body.privacy || videoPlaylist.privacy | ||
71 | if (newPrivacy === VideoPlaylistPrivacy.PUBLIC && | ||
72 | ( | ||
73 | (!videoPlaylist.videoChannelId && !body.videoChannelId) || | ||
74 | body.videoChannelId === null | ||
75 | ) | ||
76 | ) { | ||
77 | cleanUpReqFiles(req) | ||
78 | return res.status(400) | ||
79 | .json({ error: 'Cannot set "public" a playlist that is not assigned to a channel.' }) | ||
80 | } | ||
81 | |||
82 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { | ||
83 | cleanUpReqFiles(req) | ||
84 | return res.status(400) | ||
85 | .json({ error: 'Cannot update a watch later playlist.' }) | ||
86 | } | ||
87 | |||
88 | if (body.videoChannelId && !await doesVideoChannelIdExist(body.videoChannelId, res)) return cleanUpReqFiles(req) | ||
89 | |||
90 | return next() | ||
91 | } | ||
92 | ]) | ||
93 | |||
94 | const videoPlaylistsDeleteValidator = [ | ||
95 | param('playlistId') | ||
96 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
97 | |||
98 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
99 | logger.debug('Checking videoPlaylistsDeleteValidator parameters', { parameters: req.params }) | ||
100 | |||
101 | if (areValidationErrors(req, res)) return | ||
102 | |||
103 | if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return | ||
104 | |||
105 | const videoPlaylist = res.locals.videoPlaylist | ||
106 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { | ||
107 | return res.status(400) | ||
108 | .json({ error: 'Cannot delete a watch later playlist.' }) | ||
109 | } | ||
110 | |||
111 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { | ||
112 | return | ||
113 | } | ||
114 | |||
115 | return next() | ||
116 | } | ||
117 | ] | ||
118 | |||
119 | const videoPlaylistsGetValidator = [ | ||
120 | param('playlistId') | ||
121 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
122 | |||
123 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
124 | logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) | ||
125 | |||
126 | if (areValidationErrors(req, res)) return | ||
127 | |||
128 | if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return | ||
129 | |||
130 | const videoPlaylist = res.locals.videoPlaylist | ||
131 | |||
132 | // Video is unlisted, check we used the uuid to fetch it | ||
133 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { | ||
134 | if (isUUIDValid(req.params.playlistId)) return next() | ||
135 | |||
136 | return res.status(404).end() | ||
137 | } | ||
138 | |||
139 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | ||
140 | await authenticatePromiseIfNeeded(req, res) | ||
141 | |||
142 | const user = res.locals.oauth ? res.locals.oauth.token.User : null | ||
143 | if ( | ||
144 | !user || | ||
145 | (videoPlaylist.OwnerAccount.userId !== user.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) | ||
146 | ) { | ||
147 | return res.status(403) | ||
148 | .json({ error: 'Cannot get this private video playlist.' }) | ||
149 | } | ||
150 | |||
151 | return next() | ||
152 | } | ||
153 | |||
154 | return next() | ||
155 | } | ||
156 | ] | ||
157 | |||
158 | const videoPlaylistsAddVideoValidator = [ | ||
159 | param('playlistId') | ||
160 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
161 | body('videoId') | ||
162 | .custom(isIdOrUUIDValid).withMessage('Should have a valid video id/uuid'), | ||
163 | body('startTimestamp') | ||
164 | .optional() | ||
165 | .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'), | ||
166 | body('stopTimestamp') | ||
167 | .optional() | ||
168 | .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid stop timestamp'), | ||
169 | |||
170 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
171 | logger.debug('Checking videoPlaylistsAddVideoValidator parameters', { parameters: req.params }) | ||
172 | |||
173 | if (areValidationErrors(req, res)) return | ||
174 | |||
175 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | ||
176 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return | ||
177 | |||
178 | const videoPlaylist = res.locals.videoPlaylist | ||
179 | const video = res.locals.video | ||
180 | |||
181 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) | ||
182 | if (videoPlaylistElement) { | ||
183 | res.status(409) | ||
184 | .json({ error: 'This video in this playlist already exists' }) | ||
185 | .end() | ||
186 | |||
187 | return | ||
188 | } | ||
189 | |||
190 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { | ||
191 | return | ||
192 | } | ||
193 | |||
194 | return next() | ||
195 | } | ||
196 | ] | ||
197 | |||
198 | const videoPlaylistsUpdateOrRemoveVideoValidator = [ | ||
199 | param('playlistId') | ||
200 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
201 | param('videoId') | ||
202 | .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'), | ||
203 | body('startTimestamp') | ||
204 | .optional() | ||
205 | .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid start timestamp'), | ||
206 | body('stopTimestamp') | ||
207 | .optional() | ||
208 | .custom(isVideoPlaylistTimestampValid).withMessage('Should have a valid stop timestamp'), | ||
209 | |||
210 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
211 | logger.debug('Checking videoPlaylistsRemoveVideoValidator parameters', { parameters: req.params }) | ||
212 | |||
213 | if (areValidationErrors(req, res)) return | ||
214 | |||
215 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | ||
216 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return | ||
217 | |||
218 | const videoPlaylist = res.locals.videoPlaylist | ||
219 | const video = res.locals.video | ||
220 | |||
221 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) | ||
222 | if (!videoPlaylistElement) { | ||
223 | res.status(404) | ||
224 | .json({ error: 'Video playlist element not found' }) | ||
225 | .end() | ||
226 | |||
227 | return | ||
228 | } | ||
229 | res.locals.videoPlaylistElement = videoPlaylistElement | ||
230 | |||
231 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return | ||
232 | |||
233 | return next() | ||
234 | } | ||
235 | ] | ||
236 | |||
237 | const videoPlaylistElementAPGetValidator = [ | ||
238 | param('playlistId') | ||
239 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
240 | param('videoId') | ||
241 | .custom(isIdOrUUIDValid).withMessage('Should have an video id/uuid'), | ||
242 | |||
243 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
244 | logger.debug('Checking videoPlaylistElementAPGetValidator parameters', { parameters: req.params }) | ||
245 | |||
246 | if (areValidationErrors(req, res)) return | ||
247 | |||
248 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideoForAP(req.params.playlistId, req.params.videoId) | ||
249 | if (!videoPlaylistElement) { | ||
250 | res.status(404) | ||
251 | .json({ error: 'Video playlist element not found' }) | ||
252 | .end() | ||
253 | |||
254 | return | ||
255 | } | ||
256 | |||
257 | if (videoPlaylistElement.VideoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | ||
258 | return res.status(403).end() | ||
259 | } | ||
260 | |||
261 | res.locals.videoPlaylistElement = videoPlaylistElement | ||
262 | |||
263 | return next() | ||
264 | } | ||
265 | ] | ||
266 | |||
267 | const videoPlaylistsReorderVideosValidator = [ | ||
268 | param('playlistId') | ||
269 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
270 | body('startPosition') | ||
271 | .isInt({ min: 1 }).withMessage('Should have a valid start position'), | ||
272 | body('insertAfterPosition') | ||
273 | .isInt({ min: 0 }).withMessage('Should have a valid insert after position'), | ||
274 | body('reorderLength') | ||
275 | .optional() | ||
276 | .isInt({ min: 1 }).withMessage('Should have a valid range length'), | ||
277 | |||
278 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
279 | logger.debug('Checking videoPlaylistsReorderVideosValidator parameters', { parameters: req.params }) | ||
280 | |||
281 | if (areValidationErrors(req, res)) return | ||
282 | |||
283 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | ||
284 | |||
285 | const videoPlaylist = res.locals.videoPlaylist | ||
286 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return | ||
287 | |||
288 | const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id) | ||
289 | const startPosition: number = req.body.startPosition | ||
290 | const insertAfterPosition: number = req.body.insertAfterPosition | ||
291 | const reorderLength: number = req.body.reorderLength | ||
292 | |||
293 | if (startPosition >= nextPosition || insertAfterPosition >= nextPosition) { | ||
294 | res.status(400) | ||
295 | .json({ error: `Start position or insert after position exceed the playlist limits (max: ${nextPosition - 1})` }) | ||
296 | .end() | ||
297 | |||
298 | return | ||
299 | } | ||
300 | |||
301 | if (reorderLength && reorderLength + startPosition > nextPosition) { | ||
302 | res.status(400) | ||
303 | .json({ error: `Reorder length with this start position exceeds the playlist limits (max: ${nextPosition - startPosition})` }) | ||
304 | .end() | ||
305 | |||
306 | return | ||
307 | } | ||
308 | |||
309 | return next() | ||
310 | } | ||
311 | ] | ||
312 | |||
313 | const commonVideoPlaylistFiltersValidator = [ | ||
314 | query('playlistType') | ||
315 | .optional() | ||
316 | .custom(isVideoPlaylistTypeValid).withMessage('Should have a valid playlist type'), | ||
317 | |||
318 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
319 | logger.debug('Checking commonVideoPlaylistFiltersValidator parameters', { parameters: req.params }) | ||
320 | |||
321 | if (areValidationErrors(req, res)) return | ||
322 | |||
323 | return next() | ||
324 | } | ||
325 | ] | ||
326 | |||
327 | const doVideosInPlaylistExistValidator = [ | ||
328 | query('videoIds') | ||
329 | .customSanitizer(toIntArray) | ||
330 | .custom(v => isArrayOf(v, isIdValid)).withMessage('Should have a valid video ids array'), | ||
331 | |||
332 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
333 | logger.debug('Checking areVideosInPlaylistExistValidator parameters', { parameters: req.query }) | ||
334 | |||
335 | if (areValidationErrors(req, res)) return | ||
336 | |||
337 | return next() | ||
338 | } | ||
339 | ] | ||
340 | |||
341 | // --------------------------------------------------------------------------- | ||
342 | |||
343 | export { | ||
344 | videoPlaylistsAddValidator, | ||
345 | videoPlaylistsUpdateValidator, | ||
346 | videoPlaylistsDeleteValidator, | ||
347 | videoPlaylistsGetValidator, | ||
348 | |||
349 | videoPlaylistsAddVideoValidator, | ||
350 | videoPlaylistsUpdateOrRemoveVideoValidator, | ||
351 | videoPlaylistsReorderVideosValidator, | ||
352 | |||
353 | videoPlaylistElementAPGetValidator, | ||
354 | |||
355 | commonVideoPlaylistFiltersValidator, | ||
356 | |||
357 | doVideosInPlaylistExistValidator | ||
358 | } | ||
359 | |||
360 | // --------------------------------------------------------------------------- | ||
361 | |||
362 | function getCommonPlaylistEditAttributes () { | ||
363 | return [ | ||
364 | body('thumbnailfile') | ||
365 | .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( | ||
366 | 'This thumbnail file is not supported or too large. Please, make sure it is of the following type: ' | ||
367 | + CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.IMAGE.EXTNAME.join(', ') | ||
368 | ), | ||
369 | |||
370 | body('displayName') | ||
371 | .custom(isVideoPlaylistNameValid).withMessage('Should have a valid display name'), | ||
372 | body('description') | ||
373 | .optional() | ||
374 | .customSanitizer(toValueOrNull) | ||
375 | .custom(isVideoPlaylistDescriptionValid).withMessage('Should have a valid description'), | ||
376 | body('privacy') | ||
377 | .optional() | ||
378 | .toInt() | ||
379 | .custom(isVideoPlaylistPrivacyValid).withMessage('Should have correct playlist privacy'), | ||
380 | body('videoChannelId') | ||
381 | .optional() | ||
382 | .customSanitizer(toValueOrNull) | ||
383 | .toInt() | ||
384 | ] as (ValidationChain | express.Handler)[] | ||
385 | } | ||
386 | |||
387 | function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoPlaylistModel, right: UserRight, res: express.Response) { | ||
388 | if (videoPlaylist.isOwned() === false) { | ||
389 | res.status(403) | ||
390 | .json({ error: 'Cannot manage video playlist of another server.' }) | ||
391 | .end() | ||
392 | |||
393 | return false | ||
394 | } | ||
395 | |||
396 | // Check if the user can manage the video playlist | ||
397 | // The user can delete it if s/he is an admin | ||
398 | // Or if s/he is the video playlist's owner | ||
399 | if (user.hasRight(right) === false && videoPlaylist.ownerAccountId !== user.Account.id) { | ||
400 | res.status(403) | ||
401 | .json({ error: 'Cannot manage video playlist of another user' }) | ||
402 | .end() | ||
403 | |||
404 | return false | ||
405 | } | ||
406 | |||
407 | return true | ||
408 | } | ||
diff --git a/server/middlewares/validators/videos/video-rates.ts b/server/middlewares/validators/videos/video-rates.ts index 793354520..204b4a78d 100644 --- a/server/middlewares/validators/videos/video-rates.ts +++ b/server/middlewares/validators/videos/video-rates.ts | |||
@@ -1,8 +1,9 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { body, param } from 'express-validator/check' | 3 | import { body, param, query } from 'express-validator/check' |
4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' |
5 | import { isVideoExist, isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' | 5 | import { isRatingValid } from '../../../helpers/custom-validators/video-rates' |
6 | import { doesVideoExist, isVideoRatingTypeValid } from '../../../helpers/custom-validators/videos' | ||
6 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
7 | import { areValidationErrors } from '../utils' | 8 | import { areValidationErrors } from '../utils' |
8 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 9 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
@@ -17,7 +18,7 @@ const videoUpdateRateValidator = [ | |||
17 | logger.debug('Checking videoRate parameters', { parameters: req.body }) | 18 | logger.debug('Checking videoRate parameters', { parameters: req.body }) |
18 | 19 | ||
19 | if (areValidationErrors(req, res)) return | 20 | if (areValidationErrors(req, res)) return |
20 | if (!await isVideoExist(req.params.id, res)) return | 21 | if (!await doesVideoExist(req.params.id, res)) return |
21 | 22 | ||
22 | return next() | 23 | return next() |
23 | } | 24 | } |
@@ -47,9 +48,22 @@ const getAccountVideoRateValidator = function (rateType: VideoRateType) { | |||
47 | ] | 48 | ] |
48 | } | 49 | } |
49 | 50 | ||
51 | const videoRatingValidator = [ | ||
52 | query('rating').optional().custom(isRatingValid).withMessage('Value must be one of "like" or "dislike"'), | ||
53 | |||
54 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
55 | logger.debug('Checking rating parameter', { parameters: req.params }) | ||
56 | |||
57 | if (areValidationErrors(req, res)) return | ||
58 | |||
59 | return next() | ||
60 | } | ||
61 | ] | ||
62 | |||
50 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
51 | 64 | ||
52 | export { | 65 | export { |
53 | videoUpdateRateValidator, | 66 | videoUpdateRateValidator, |
54 | getAccountVideoRateValidator | 67 | getAccountVideoRateValidator, |
68 | videoRatingValidator | ||
55 | } | 69 | } |
diff --git a/server/middlewares/validators/videos/video-shares.ts b/server/middlewares/validators/videos/video-shares.ts index 646d7acb1..d5cbdb03e 100644 --- a/server/middlewares/validators/videos/video-shares.ts +++ b/server/middlewares/validators/videos/video-shares.ts | |||
@@ -2,11 +2,10 @@ import * as express from 'express' | |||
2 | import 'express-validator' | 2 | import 'express-validator' |
3 | import { param } from 'express-validator/check' | 3 | import { param } from 'express-validator/check' |
4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' |
5 | import { isVideoExist } from '../../../helpers/custom-validators/videos' | 5 | import { doesVideoExist } from '../../../helpers/custom-validators/videos' |
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { VideoShareModel } from '../../../models/video/video-share' | 7 | import { VideoShareModel } from '../../../models/video/video-share' |
8 | import { areValidationErrors } from '../utils' | 8 | import { areValidationErrors } from '../utils' |
9 | import { VideoModel } from '../../../models/video/video' | ||
10 | 9 | ||
11 | const videosShareValidator = [ | 10 | const videosShareValidator = [ |
12 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 11 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
@@ -16,9 +15,9 @@ const videosShareValidator = [ | |||
16 | logger.debug('Checking videoShare parameters', { parameters: req.params }) | 15 | logger.debug('Checking videoShare parameters', { parameters: req.params }) |
17 | 16 | ||
18 | if (areValidationErrors(req, res)) return | 17 | if (areValidationErrors(req, res)) return |
19 | if (!await isVideoExist(req.params.id, res)) return | 18 | if (!await doesVideoExist(req.params.id, res)) return |
20 | 19 | ||
21 | const video: VideoModel = res.locals.video | 20 | const video = res.locals.video |
22 | 21 | ||
23 | const share = await VideoShareModel.load(req.params.actorId, video.id) | 22 | const share = await VideoShareModel.load(req.params.actorId, video.id) |
24 | if (!share) { | 23 | if (!share) { |
diff --git a/server/middlewares/validators/videos/video-watch.ts b/server/middlewares/validators/videos/video-watch.ts index c38ad8a10..a3a800d14 100644 --- a/server/middlewares/validators/videos/video-watch.ts +++ b/server/middlewares/validators/videos/video-watch.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | import { body, param } from 'express-validator/check' | 1 | import { body, param } from 'express-validator/check' |
2 | import * as express from 'express' | 2 | import * as express from 'express' |
3 | import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' | 3 | import { isIdOrUUIDValid } from '../../../helpers/custom-validators/misc' |
4 | import { isVideoExist } from '../../../helpers/custom-validators/videos' | 4 | import { doesVideoExist } from '../../../helpers/custom-validators/videos' |
5 | import { areValidationErrors } from '../utils' | 5 | import { areValidationErrors } from '../utils' |
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { UserModel } from '../../../models/account/user' | ||
8 | 7 | ||
9 | const videoWatchingValidator = [ | 8 | const videoWatchingValidator = [ |
10 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 9 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
@@ -16,9 +15,9 @@ const videoWatchingValidator = [ | |||
16 | logger.debug('Checking videoWatching parameters', { parameters: req.body }) | 15 | logger.debug('Checking videoWatching parameters', { parameters: req.body }) |
17 | 16 | ||
18 | if (areValidationErrors(req, res)) return | 17 | if (areValidationErrors(req, res)) return |
19 | if (!await isVideoExist(req.params.videoId, res, 'id')) return | 18 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return |
20 | 19 | ||
21 | const user = res.locals.oauth.token.User as UserModel | 20 | const user = res.locals.oauth.token.User |
22 | if (user.videosHistoryEnabled === false) { | 21 | if (user.videosHistoryEnabled === false) { |
23 | logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id) | 22 | logger.warn('Cannot set videos to watch by user %d: videos history is disabled.', user.id) |
24 | return res.status(409).end() | 23 | return res.status(409).end() |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index 051a19e16..2b01f108d 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -14,38 +14,38 @@ import { | |||
14 | } from '../../../helpers/custom-validators/misc' | 14 | } from '../../../helpers/custom-validators/misc' |
15 | import { | 15 | import { |
16 | checkUserCanManageVideo, | 16 | checkUserCanManageVideo, |
17 | doesVideoChannelOfAccountExist, | ||
18 | doesVideoExist, | ||
17 | isScheduleVideoUpdatePrivacyValid, | 19 | isScheduleVideoUpdatePrivacyValid, |
18 | isVideoCategoryValid, | 20 | isVideoCategoryValid, |
19 | isVideoChannelOfAccountExist, | ||
20 | isVideoDescriptionValid, | 21 | isVideoDescriptionValid, |
21 | isVideoExist, | ||
22 | isVideoFile, | 22 | isVideoFile, |
23 | isVideoFilterValid, | 23 | isVideoFilterValid, |
24 | isVideoImage, | 24 | isVideoImage, |
25 | isVideoLanguageValid, | 25 | isVideoLanguageValid, |
26 | isVideoLicenceValid, | 26 | isVideoLicenceValid, |
27 | isVideoNameValid, | 27 | isVideoNameValid, |
28 | isVideoOriginallyPublishedAtValid, | ||
28 | isVideoPrivacyValid, | 29 | isVideoPrivacyValid, |
29 | isVideoSupportValid, | 30 | isVideoSupportValid, |
30 | isVideoTagsValid | 31 | isVideoTagsValid |
31 | } from '../../../helpers/custom-validators/videos' | 32 | } from '../../../helpers/custom-validators/videos' |
32 | import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils' | 33 | import { getDurationFromVideoFile } from '../../../helpers/ffmpeg-utils' |
33 | import { logger } from '../../../helpers/logger' | 34 | import { logger } from '../../../helpers/logger' |
34 | import { CONFIG, CONSTRAINTS_FIELDS } from '../../../initializers' | 35 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
35 | import { authenticatePromiseIfNeeded } from '../../oauth' | 36 | import { authenticatePromiseIfNeeded } from '../../oauth' |
36 | import { areValidationErrors } from '../utils' | 37 | import { areValidationErrors } from '../utils' |
37 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 38 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
38 | import { VideoModel } from '../../../models/video/video' | 39 | import { VideoModel } from '../../../models/video/video' |
39 | import { UserModel } from '../../../models/account/user' | ||
40 | import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' | 40 | import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' |
41 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' | 41 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' |
42 | import { VideoChangeOwnershipModel } from '../../../models/video/video-change-ownership' | ||
43 | import { AccountModel } from '../../../models/account/account' | 42 | import { AccountModel } from '../../../models/account/account' |
44 | import { VideoFetchType } from '../../../helpers/video' | 43 | import { VideoFetchType } from '../../../helpers/video' |
45 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' | 44 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' |
46 | import { getServerActor } from '../../../helpers/utils' | 45 | import { getServerActor } from '../../../helpers/utils' |
46 | import { CONFIG } from '../../../initializers/config' | ||
47 | 47 | ||
48 | const videosAddValidator = getCommonVideoAttributes().concat([ | 48 | const videosAddValidator = getCommonVideoEditAttributes().concat([ |
49 | body('videofile') | 49 | body('videofile') |
50 | .custom((value, { req }) => isVideoFile(req.files)).withMessage( | 50 | .custom((value, { req }) => isVideoFile(req.files)).withMessage( |
51 | 'This file is not supported or too large. Please, make sure it is of the following type: ' | 51 | 'This file is not supported or too large. Please, make sure it is of the following type: ' |
@@ -65,9 +65,10 @@ const videosAddValidator = getCommonVideoAttributes().concat([ | |||
65 | const videoFile: Express.Multer.File = req.files['videofile'][0] | 65 | const videoFile: Express.Multer.File = req.files['videofile'][0] |
66 | const user = res.locals.oauth.token.User | 66 | const user = res.locals.oauth.token.User |
67 | 67 | ||
68 | if (!await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | 68 | if (!await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) |
69 | 69 | ||
70 | const isAble = await user.isAbleToUploadVideo(videoFile) | 70 | const isAble = await user.isAbleToUploadVideo(videoFile) |
71 | |||
71 | if (isAble === false) { | 72 | if (isAble === false) { |
72 | res.status(403) | 73 | res.status(403) |
73 | .json({ error: 'The user video quota is exceeded with this video.' }) | 74 | .json({ error: 'The user video quota is exceeded with this video.' }) |
@@ -93,7 +94,7 @@ const videosAddValidator = getCommonVideoAttributes().concat([ | |||
93 | } | 94 | } |
94 | ]) | 95 | ]) |
95 | 96 | ||
96 | const videosUpdateValidator = getCommonVideoAttributes().concat([ | 97 | const videosUpdateValidator = getCommonVideoEditAttributes().concat([ |
97 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 98 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
98 | body('name') | 99 | body('name') |
99 | .optional() | 100 | .optional() |
@@ -108,7 +109,7 @@ const videosUpdateValidator = getCommonVideoAttributes().concat([ | |||
108 | 109 | ||
109 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) | 110 | if (areValidationErrors(req, res)) return cleanUpReqFiles(req) |
110 | if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) | 111 | if (areErrorsInScheduleUpdate(req, res)) return cleanUpReqFiles(req) |
111 | if (!await isVideoExist(req.params.id, res)) return cleanUpReqFiles(req) | 112 | if (!await doesVideoExist(req.params.id, res)) return cleanUpReqFiles(req) |
112 | 113 | ||
113 | const video = res.locals.video | 114 | const video = res.locals.video |
114 | 115 | ||
@@ -122,14 +123,14 @@ const videosUpdateValidator = getCommonVideoAttributes().concat([ | |||
122 | .json({ error: 'Cannot set "private" a video that was not private.' }) | 123 | .json({ error: 'Cannot set "private" a video that was not private.' }) |
123 | } | 124 | } |
124 | 125 | ||
125 | if (req.body.channelId && !await isVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | 126 | if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) |
126 | 127 | ||
127 | return next() | 128 | return next() |
128 | } | 129 | } |
129 | ]) | 130 | ]) |
130 | 131 | ||
131 | async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) { | 132 | async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) { |
132 | const video: VideoModel = res.locals.video | 133 | const video = res.locals.video |
133 | 134 | ||
134 | // Anybody can watch local videos | 135 | // Anybody can watch local videos |
135 | if (video.isOwned() === true) return next() | 136 | if (video.isOwned() === true) return next() |
@@ -161,15 +162,15 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => { | |||
161 | logger.debug('Checking videosGet parameters', { parameters: req.params }) | 162 | logger.debug('Checking videosGet parameters', { parameters: req.params }) |
162 | 163 | ||
163 | if (areValidationErrors(req, res)) return | 164 | if (areValidationErrors(req, res)) return |
164 | if (!await isVideoExist(req.params.id, res, fetchType)) return | 165 | if (!await doesVideoExist(req.params.id, res, fetchType)) return |
165 | 166 | ||
166 | const video: VideoModel = res.locals.video | 167 | const video = res.locals.video |
167 | 168 | ||
168 | // Video private or blacklisted | 169 | // Video private or blacklisted |
169 | if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { | 170 | if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { |
170 | await authenticatePromiseIfNeeded(req, res) | 171 | await authenticatePromiseIfNeeded(req, res) |
171 | 172 | ||
172 | const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : null | 173 | const user = res.locals.oauth ? res.locals.oauth.token.User : null |
173 | 174 | ||
174 | // Only the owner or a user that have blacklist rights can see the video | 175 | // Only the owner or a user that have blacklist rights can see the video |
175 | if ( | 176 | if ( |
@@ -206,7 +207,7 @@ const videosRemoveValidator = [ | |||
206 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) | 207 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) |
207 | 208 | ||
208 | if (areValidationErrors(req, res)) return | 209 | if (areValidationErrors(req, res)) return |
209 | if (!await isVideoExist(req.params.id, res)) return | 210 | if (!await doesVideoExist(req.params.id, res)) return |
210 | 211 | ||
211 | // Check if the user who did the request is able to delete the video | 212 | // Check if the user who did the request is able to delete the video |
212 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return | 213 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return |
@@ -222,7 +223,7 @@ const videosChangeOwnershipValidator = [ | |||
222 | logger.debug('Checking changeOwnership parameters', { parameters: req.params }) | 223 | logger.debug('Checking changeOwnership parameters', { parameters: req.params }) |
223 | 224 | ||
224 | if (areValidationErrors(req, res)) return | 225 | if (areValidationErrors(req, res)) return |
225 | if (!await isVideoExist(req.params.videoId, res)) return | 226 | if (!await doesVideoExist(req.params.videoId, res)) return |
226 | 227 | ||
227 | // Check if the user who did the request is able to change the ownership of the video | 228 | // Check if the user who did the request is able to change the ownership of the video |
228 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return | 229 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return |
@@ -255,7 +256,7 @@ const videosTerminateChangeOwnershipValidator = [ | |||
255 | return next() | 256 | return next() |
256 | }, | 257 | }, |
257 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 258 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
258 | const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel | 259 | const videoChangeOwnership = res.locals.videoChangeOwnership |
259 | 260 | ||
260 | if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { | 261 | if (videoChangeOwnership.status === VideoChangeOwnershipStatus.WAITING) { |
261 | return next() | 262 | return next() |
@@ -271,10 +272,10 @@ const videosTerminateChangeOwnershipValidator = [ | |||
271 | const videosAcceptChangeOwnershipValidator = [ | 272 | const videosAcceptChangeOwnershipValidator = [ |
272 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 273 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
273 | const body = req.body as VideoChangeOwnershipAccept | 274 | const body = req.body as VideoChangeOwnershipAccept |
274 | if (!await isVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return | 275 | if (!await doesVideoChannelOfAccountExist(body.channelId, res.locals.oauth.token.User, res)) return |
275 | 276 | ||
276 | const user = res.locals.oauth.token.User | 277 | const user = res.locals.oauth.token.User |
277 | const videoChangeOwnership = res.locals.videoChangeOwnership as VideoChangeOwnershipModel | 278 | const videoChangeOwnership = res.locals.videoChangeOwnership |
278 | const isAble = await user.isAbleToUploadVideo(videoChangeOwnership.Video.getOriginalFile()) | 279 | const isAble = await user.isAbleToUploadVideo(videoChangeOwnership.Video.getOriginalFile()) |
279 | if (isAble === false) { | 280 | if (isAble === false) { |
280 | res.status(403) | 281 | res.status(403) |
@@ -287,7 +288,7 @@ const videosAcceptChangeOwnershipValidator = [ | |||
287 | } | 288 | } |
288 | ] | 289 | ] |
289 | 290 | ||
290 | function getCommonVideoAttributes () { | 291 | function getCommonVideoEditAttributes () { |
291 | return [ | 292 | return [ |
292 | body('thumbnailfile') | 293 | body('thumbnailfile') |
293 | .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( | 294 | .custom((value, { req }) => isVideoImage(req.files, 'thumbnailfile')).withMessage( |
@@ -340,7 +341,14 @@ function getCommonVideoAttributes () { | |||
340 | .optional() | 341 | .optional() |
341 | .toBoolean() | 342 | .toBoolean() |
342 | .custom(isBooleanValid).withMessage('Should have comments enabled boolean'), | 343 | .custom(isBooleanValid).withMessage('Should have comments enabled boolean'), |
343 | 344 | body('downloadEnabled') | |
345 | .optional() | ||
346 | .toBoolean() | ||
347 | .custom(isBooleanValid).withMessage('Should have downloading enabled boolean'), | ||
348 | body('originallyPublishedAt') | ||
349 | .optional() | ||
350 | .customSanitizer(toValueOrNull) | ||
351 | .custom(isVideoOriginallyPublishedAtValid).withMessage('Should have a valid original publication date'), | ||
344 | body('scheduleUpdate') | 352 | body('scheduleUpdate') |
345 | .optional() | 353 | .optional() |
346 | .customSanitizer(toValueOrNull), | 354 | .customSanitizer(toValueOrNull), |
@@ -387,7 +395,7 @@ const commonVideosFiltersValidator = [ | |||
387 | 395 | ||
388 | if (areValidationErrors(req, res)) return | 396 | if (areValidationErrors(req, res)) return |
389 | 397 | ||
390 | const user: UserModel = res.locals.oauth ? res.locals.oauth.token.User : undefined | 398 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
391 | if (req.query.filter === 'all-local' && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)) { | 399 | if (req.query.filter === 'all-local' && (!user || user.hasRight(UserRight.SEE_ALL_VIDEOS) === false)) { |
392 | res.status(401) | 400 | res.status(401) |
393 | .json({ error: 'You are not allowed to see all local videos.' }) | 401 | .json({ error: 'You are not allowed to see all local videos.' }) |
@@ -413,7 +421,7 @@ export { | |||
413 | videosTerminateChangeOwnershipValidator, | 421 | videosTerminateChangeOwnershipValidator, |
414 | videosAcceptChangeOwnershipValidator, | 422 | videosAcceptChangeOwnershipValidator, |
415 | 423 | ||
416 | getCommonVideoAttributes, | 424 | getCommonVideoEditAttributes, |
417 | 425 | ||
418 | commonVideosFiltersValidator | 426 | commonVideosFiltersValidator |
419 | } | 427 | } |
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index efd6ed59e..d5746ad76 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -8,22 +8,22 @@ enum ScopeNames { | |||
8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' | 8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' |
9 | } | 9 | } |
10 | 10 | ||
11 | @Scopes({ | 11 | @Scopes(() => ({ |
12 | [ScopeNames.WITH_ACCOUNTS]: { | 12 | [ScopeNames.WITH_ACCOUNTS]: { |
13 | include: [ | 13 | include: [ |
14 | { | 14 | { |
15 | model: () => AccountModel, | 15 | model: AccountModel, |
16 | required: true, | 16 | required: true, |
17 | as: 'ByAccount' | 17 | as: 'ByAccount' |
18 | }, | 18 | }, |
19 | { | 19 | { |
20 | model: () => AccountModel, | 20 | model: AccountModel, |
21 | required: true, | 21 | required: true, |
22 | as: 'BlockedAccount' | 22 | as: 'BlockedAccount' |
23 | } | 23 | } |
24 | ] | 24 | ] |
25 | } | 25 | } |
26 | }) | 26 | })) |
27 | 27 | ||
28 | @Table({ | 28 | @Table({ |
29 | tableName: 'accountBlocklist', | 29 | tableName: 'accountBlocklist', |
@@ -83,7 +83,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
83 | attributes: [ 'accountId', 'id' ], | 83 | attributes: [ 'accountId', 'id' ], |
84 | where: { | 84 | where: { |
85 | accountId: { | 85 | accountId: { |
86 | [Op.any]: accountIds | 86 | [Op.in]: accountIds // FIXME: sequelize ANY seems broken |
87 | }, | 87 | }, |
88 | targetAccountId | 88 | targetAccountId |
89 | }, | 89 | }, |
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index 18762f0c5..59f586b54 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -1,14 +1,15 @@ | |||
1 | import { values } from 'lodash' | 1 | import { values } from 'lodash' |
2 | import { Transaction } from 'sequelize' | 2 | import { FindOptions, Op, Transaction } from 'sequelize' |
3 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
4 | import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions' | ||
5 | import { VideoRateType } from '../../../shared/models/videos' | 4 | import { VideoRateType } from '../../../shared/models/videos' |
6 | import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers' | 5 | import { CONSTRAINTS_FIELDS, VIDEO_RATE_TYPES } from '../../initializers/constants' |
7 | import { VideoModel } from '../video/video' | 6 | import { VideoModel } from '../video/video' |
8 | import { AccountModel } from './account' | 7 | import { AccountModel } from './account' |
9 | import { ActorModel } from '../activitypub/actor' | 8 | import { ActorModel } from '../activitypub/actor' |
10 | import { throwIfNotValid } from '../utils' | 9 | import { getSort, throwIfNotValid } from '../utils' |
11 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
11 | import { AccountVideoRate } from '../../../shared' | ||
12 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from '../video/video-channel' | ||
12 | 13 | ||
13 | /* | 14 | /* |
14 | Account rates per video. | 15 | Account rates per video. |
@@ -38,7 +39,7 @@ import { isActivityPubUrlValid } from '../../helpers/custom-validators/activityp | |||
38 | export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | 39 | export class AccountVideoRateModel extends Model<AccountVideoRateModel> { |
39 | 40 | ||
40 | @AllowNull(false) | 41 | @AllowNull(false) |
41 | @Column(DataType.ENUM(values(VIDEO_RATE_TYPES))) | 42 | @Column(DataType.ENUM(...values(VIDEO_RATE_TYPES))) |
42 | type: VideoRateType | 43 | type: VideoRateType |
43 | 44 | ||
44 | @AllowNull(false) | 45 | @AllowNull(false) |
@@ -77,7 +78,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
77 | Account: AccountModel | 78 | Account: AccountModel |
78 | 79 | ||
79 | static load (accountId: number, videoId: number, transaction?: Transaction) { | 80 | static load (accountId: number, videoId: number, transaction?: Transaction) { |
80 | const options: IFindOptions<AccountVideoRateModel> = { | 81 | const options: FindOptions = { |
81 | where: { | 82 | where: { |
82 | accountId, | 83 | accountId, |
83 | videoId | 84 | videoId |
@@ -88,8 +89,40 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
88 | return AccountVideoRateModel.findOne(options) | 89 | return AccountVideoRateModel.findOne(options) |
89 | } | 90 | } |
90 | 91 | ||
92 | static listByAccountForApi (options: { | ||
93 | start: number, | ||
94 | count: number, | ||
95 | sort: string, | ||
96 | type?: string, | ||
97 | accountId: number | ||
98 | }) { | ||
99 | const query: FindOptions = { | ||
100 | offset: options.start, | ||
101 | limit: options.count, | ||
102 | order: getSort(options.sort), | ||
103 | where: { | ||
104 | accountId: options.accountId | ||
105 | }, | ||
106 | include: [ | ||
107 | { | ||
108 | model: VideoModel, | ||
109 | required: true, | ||
110 | include: [ | ||
111 | { | ||
112 | model: VideoChannelModel.scope({ method: [VideoChannelScopeNames.SUMMARY, true] }), | ||
113 | required: true | ||
114 | } | ||
115 | ] | ||
116 | } | ||
117 | ] | ||
118 | } | ||
119 | if (options.type) query.where['type'] = options.type | ||
120 | |||
121 | return AccountVideoRateModel.findAndCountAll(query) | ||
122 | } | ||
123 | |||
91 | static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) { | 124 | static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) { |
92 | const options: IFindOptions<AccountVideoRateModel> = { | 125 | const options: FindOptions = { |
93 | where: { | 126 | where: { |
94 | videoId, | 127 | videoId, |
95 | type: rateType | 128 | type: rateType |
@@ -121,7 +154,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
121 | } | 154 | } |
122 | 155 | ||
123 | static loadByUrl (url: string, transaction: Transaction) { | 156 | static loadByUrl (url: string, transaction: Transaction) { |
124 | const options: IFindOptions<AccountVideoRateModel> = { | 157 | const options: FindOptions = { |
125 | where: { | 158 | where: { |
126 | url | 159 | url |
127 | } | 160 | } |
@@ -158,4 +191,38 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
158 | 191 | ||
159 | return AccountVideoRateModel.findAndCountAll(query) | 192 | return AccountVideoRateModel.findAndCountAll(query) |
160 | } | 193 | } |
194 | |||
195 | static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) { | ||
196 | return AccountVideoRateModel.sequelize.transaction(async t => { | ||
197 | const query = { | ||
198 | where: { | ||
199 | updatedAt: { | ||
200 | [Op.lt]: beforeUpdatedAt | ||
201 | }, | ||
202 | videoId, | ||
203 | type | ||
204 | }, | ||
205 | transaction: t | ||
206 | } | ||
207 | |||
208 | const deleted = await AccountVideoRateModel.destroy(query) | ||
209 | |||
210 | const options = { | ||
211 | transaction: t, | ||
212 | where: { | ||
213 | id: videoId | ||
214 | } | ||
215 | } | ||
216 | |||
217 | if (type === 'like') await VideoModel.increment({ likes: -deleted }, options) | ||
218 | else if (type === 'dislike') await VideoModel.increment({ dislikes: -deleted }, options) | ||
219 | }) | ||
220 | } | ||
221 | |||
222 | toFormattedJSON (): AccountVideoRate { | ||
223 | return { | ||
224 | video: this.Video.toFormattedJSON(), | ||
225 | rating: this.type | ||
226 | } | ||
227 | } | ||
161 | } | 228 | } |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 84ef0b30d..2b04acd86 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -1,20 +1,20 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { | 1 | import { |
3 | AllowNull, | 2 | AllowNull, |
4 | BeforeDestroy, | 3 | BeforeDestroy, |
5 | BelongsTo, | 4 | BelongsTo, |
6 | Column, | 5 | Column, |
7 | CreatedAt, | 6 | CreatedAt, DataType, |
8 | Default, | 7 | Default, |
9 | DefaultScope, | 8 | DefaultScope, |
10 | ForeignKey, | 9 | ForeignKey, |
11 | HasMany, | 10 | HasMany, |
12 | Is, | 11 | Is, |
13 | Model, | 12 | Model, |
13 | Scopes, | ||
14 | Table, | 14 | Table, |
15 | UpdatedAt | 15 | UpdatedAt |
16 | } from 'sequelize-typescript' | 16 | } from 'sequelize-typescript' |
17 | import { Account } from '../../../shared/models/actors' | 17 | import { Account, AccountSummary } from '../../../shared/models/actors' |
18 | import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' | 18 | import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' |
19 | import { sendDeleteActor } from '../../lib/activitypub/send' | 19 | import { sendDeleteActor } from '../../lib/activitypub/send' |
20 | import { ActorModel } from '../activitypub/actor' | 20 | import { ActorModel } from '../activitypub/actor' |
@@ -24,15 +24,49 @@ import { getSort, throwIfNotValid } from '../utils' | |||
24 | import { VideoChannelModel } from '../video/video-channel' | 24 | import { VideoChannelModel } from '../video/video-channel' |
25 | import { VideoCommentModel } from '../video/video-comment' | 25 | import { VideoCommentModel } from '../video/video-comment' |
26 | import { UserModel } from './user' | 26 | import { UserModel } from './user' |
27 | import { AvatarModel } from '../avatar/avatar' | ||
28 | import { VideoPlaylistModel } from '../video/video-playlist' | ||
29 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | ||
30 | import { Op, Transaction, WhereOptions } from 'sequelize' | ||
27 | 31 | ||
28 | @DefaultScope({ | 32 | export enum ScopeNames { |
33 | SUMMARY = 'SUMMARY' | ||
34 | } | ||
35 | |||
36 | @DefaultScope(() => ({ | ||
29 | include: [ | 37 | include: [ |
30 | { | 38 | { |
31 | model: () => ActorModel, // Default scope includes avatar and server | 39 | model: ActorModel, // Default scope includes avatar and server |
32 | required: true | 40 | required: true |
33 | } | 41 | } |
34 | ] | 42 | ] |
35 | }) | 43 | })) |
44 | @Scopes(() => ({ | ||
45 | [ ScopeNames.SUMMARY ]: (whereActor?: WhereOptions) => { | ||
46 | return { | ||
47 | attributes: [ 'id', 'name' ], | ||
48 | include: [ | ||
49 | { | ||
50 | attributes: [ 'id', 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], | ||
51 | model: ActorModel.unscoped(), | ||
52 | required: true, | ||
53 | where: whereActor, | ||
54 | include: [ | ||
55 | { | ||
56 | attributes: [ 'host' ], | ||
57 | model: ServerModel.unscoped(), | ||
58 | required: false | ||
59 | }, | ||
60 | { | ||
61 | model: AvatarModel.unscoped(), | ||
62 | required: false | ||
63 | } | ||
64 | ] | ||
65 | } | ||
66 | ] | ||
67 | } | ||
68 | } | ||
69 | })) | ||
36 | @Table({ | 70 | @Table({ |
37 | tableName: 'account', | 71 | tableName: 'account', |
38 | indexes: [ | 72 | indexes: [ |
@@ -56,8 +90,8 @@ export class AccountModel extends Model<AccountModel> { | |||
56 | 90 | ||
57 | @AllowNull(true) | 91 | @AllowNull(true) |
58 | @Default(null) | 92 | @Default(null) |
59 | @Is('AccountDescription', value => throwIfNotValid(value, isAccountDescriptionValid, 'description')) | 93 | @Is('AccountDescription', value => throwIfNotValid(value, isAccountDescriptionValid, 'description', true)) |
60 | @Column | 94 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max)) |
61 | description: string | 95 | description: string |
62 | 96 | ||
63 | @CreatedAt | 97 | @CreatedAt |
@@ -111,6 +145,15 @@ export class AccountModel extends Model<AccountModel> { | |||
111 | }) | 145 | }) |
112 | VideoChannels: VideoChannelModel[] | 146 | VideoChannels: VideoChannelModel[] |
113 | 147 | ||
148 | @HasMany(() => VideoPlaylistModel, { | ||
149 | foreignKey: { | ||
150 | allowNull: false | ||
151 | }, | ||
152 | onDelete: 'cascade', | ||
153 | hooks: true | ||
154 | }) | ||
155 | VideoPlaylists: VideoPlaylistModel[] | ||
156 | |||
114 | @HasMany(() => VideoCommentModel, { | 157 | @HasMany(() => VideoCommentModel, { |
115 | foreignKey: { | 158 | foreignKey: { |
116 | allowNull: false | 159 | allowNull: false |
@@ -133,8 +176,8 @@ export class AccountModel extends Model<AccountModel> { | |||
133 | return undefined | 176 | return undefined |
134 | } | 177 | } |
135 | 178 | ||
136 | static load (id: number, transaction?: Sequelize.Transaction) { | 179 | static load (id: number, transaction?: Transaction) { |
137 | return AccountModel.findById(id, { transaction }) | 180 | return AccountModel.findByPk(id, { transaction }) |
138 | } | 181 | } |
139 | 182 | ||
140 | static loadByUUID (uuid: string) { | 183 | static loadByUUID (uuid: string) { |
@@ -153,18 +196,26 @@ export class AccountModel extends Model<AccountModel> { | |||
153 | return AccountModel.findOne(query) | 196 | return AccountModel.findOne(query) |
154 | } | 197 | } |
155 | 198 | ||
199 | static loadByNameWithHost (nameWithHost: string) { | ||
200 | const [ accountName, host ] = nameWithHost.split('@') | ||
201 | |||
202 | if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName) | ||
203 | |||
204 | return AccountModel.loadByNameAndHost(accountName, host) | ||
205 | } | ||
206 | |||
156 | static loadLocalByName (name: string) { | 207 | static loadLocalByName (name: string) { |
157 | const query = { | 208 | const query = { |
158 | where: { | 209 | where: { |
159 | [ Sequelize.Op.or ]: [ | 210 | [ Op.or ]: [ |
160 | { | 211 | { |
161 | userId: { | 212 | userId: { |
162 | [ Sequelize.Op.ne ]: null | 213 | [ Op.ne ]: null |
163 | } | 214 | } |
164 | }, | 215 | }, |
165 | { | 216 | { |
166 | applicationId: { | 217 | applicationId: { |
167 | [ Sequelize.Op.ne ]: null | 218 | [ Op.ne ]: null |
168 | } | 219 | } |
169 | } | 220 | } |
170 | ] | 221 | ] |
@@ -208,7 +259,7 @@ export class AccountModel extends Model<AccountModel> { | |||
208 | return AccountModel.findOne(query) | 259 | return AccountModel.findOne(query) |
209 | } | 260 | } |
210 | 261 | ||
211 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | 262 | static loadByUrl (url: string, transaction?: Transaction) { |
212 | const query = { | 263 | const query = { |
213 | include: [ | 264 | include: [ |
214 | { | 265 | { |
@@ -276,6 +327,20 @@ export class AccountModel extends Model<AccountModel> { | |||
276 | return Object.assign(actor, account) | 327 | return Object.assign(actor, account) |
277 | } | 328 | } |
278 | 329 | ||
330 | toFormattedSummaryJSON (): AccountSummary { | ||
331 | const actor = this.Actor.toFormattedJSON() | ||
332 | |||
333 | return { | ||
334 | id: this.id, | ||
335 | uuid: actor.uuid, | ||
336 | name: actor.name, | ||
337 | displayName: this.getDisplayName(), | ||
338 | url: actor.url, | ||
339 | host: actor.host, | ||
340 | avatar: actor.avatar | ||
341 | } | ||
342 | } | ||
343 | |||
279 | toActivityPubObject () { | 344 | toActivityPubObject () { |
280 | const obj = this.Actor.toActivityPubObject(this.name, 'Account') | 345 | const obj = this.Actor.toActivityPubObject(this.name, 'Account') |
281 | 346 | ||
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts index f1c3ac223..c2fbc6d23 100644 --- a/server/models/account/user-notification-setting.ts +++ b/server/models/account/user-notification-setting.ts | |||
@@ -59,6 +59,15 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
59 | @AllowNull(false) | 59 | @AllowNull(false) |
60 | @Default(null) | 60 | @Default(null) |
61 | @Is( | 61 | @Is( |
62 | 'UserNotificationSettingVideoAutoBlacklistAsModerator', | ||
63 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'videoAutoBlacklistAsModerator') | ||
64 | ) | ||
65 | @Column | ||
66 | videoAutoBlacklistAsModerator: UserNotificationSettingValue | ||
67 | |||
68 | @AllowNull(false) | ||
69 | @Default(null) | ||
70 | @Is( | ||
62 | 'UserNotificationSettingBlacklistOnMyVideo', | 71 | 'UserNotificationSettingBlacklistOnMyVideo', |
63 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'blacklistOnMyVideo') | 72 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'blacklistOnMyVideo') |
64 | ) | 73 | ) |
@@ -95,6 +104,15 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
95 | @AllowNull(false) | 104 | @AllowNull(false) |
96 | @Default(null) | 105 | @Default(null) |
97 | @Is( | 106 | @Is( |
107 | 'UserNotificationSettingNewInstanceFollower', | ||
108 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newInstanceFollower') | ||
109 | ) | ||
110 | @Column | ||
111 | newInstanceFollower: UserNotificationSettingValue | ||
112 | |||
113 | @AllowNull(false) | ||
114 | @Default(null) | ||
115 | @Is( | ||
98 | 'UserNotificationSettingNewFollow', | 116 | 'UserNotificationSettingNewFollow', |
99 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') | 117 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') |
100 | ) | 118 | ) |
@@ -139,12 +157,14 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
139 | newCommentOnMyVideo: this.newCommentOnMyVideo, | 157 | newCommentOnMyVideo: this.newCommentOnMyVideo, |
140 | newVideoFromSubscription: this.newVideoFromSubscription, | 158 | newVideoFromSubscription: this.newVideoFromSubscription, |
141 | videoAbuseAsModerator: this.videoAbuseAsModerator, | 159 | videoAbuseAsModerator: this.videoAbuseAsModerator, |
160 | videoAutoBlacklistAsModerator: this.videoAutoBlacklistAsModerator, | ||
142 | blacklistOnMyVideo: this.blacklistOnMyVideo, | 161 | blacklistOnMyVideo: this.blacklistOnMyVideo, |
143 | myVideoPublished: this.myVideoPublished, | 162 | myVideoPublished: this.myVideoPublished, |
144 | myVideoImportFinished: this.myVideoImportFinished, | 163 | myVideoImportFinished: this.myVideoImportFinished, |
145 | newUserRegistration: this.newUserRegistration, | 164 | newUserRegistration: this.newUserRegistration, |
146 | commentMention: this.commentMention, | 165 | commentMention: this.commentMention, |
147 | newFollow: this.newFollow | 166 | newFollow: this.newFollow, |
167 | newInstanceFollower: this.newInstanceFollower | ||
148 | } | 168 | } |
149 | } | 169 | } |
150 | } | 170 | } |
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index 6cdbb827b..a4f97037b 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts | |||
@@ -1,17 +1,4 @@ | |||
1 | import { | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
2 | AllowNull, | ||
3 | BelongsTo, | ||
4 | Column, | ||
5 | CreatedAt, | ||
6 | Default, | ||
7 | ForeignKey, | ||
8 | IFindOptions, | ||
9 | Is, | ||
10 | Model, | ||
11 | Scopes, | ||
12 | Table, | ||
13 | UpdatedAt | ||
14 | } from 'sequelize-typescript' | ||
15 | import { UserNotification, UserNotificationType } from '../../../shared' | 2 | import { UserNotification, UserNotificationType } from '../../../shared' |
16 | import { getSort, throwIfNotValid } from '../utils' | 3 | import { getSort, throwIfNotValid } from '../utils' |
17 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | 4 | import { isBooleanValid } from '../../helpers/custom-validators/misc' |
@@ -19,7 +6,7 @@ import { isUserNotificationTypeValid } from '../../helpers/custom-validators/use | |||
19 | import { UserModel } from './user' | 6 | import { UserModel } from './user' |
20 | import { VideoModel } from '../video/video' | 7 | import { VideoModel } from '../video/video' |
21 | import { VideoCommentModel } from '../video/video-comment' | 8 | import { VideoCommentModel } from '../video/video-comment' |
22 | import { Op } from 'sequelize' | 9 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' |
23 | import { VideoChannelModel } from '../video/video-channel' | 10 | import { VideoChannelModel } from '../video/video-channel' |
24 | import { AccountModel } from './account' | 11 | import { AccountModel } from './account' |
25 | import { VideoAbuseModel } from '../video/video-abuse' | 12 | import { VideoAbuseModel } from '../video/video-abuse' |
@@ -37,17 +24,17 @@ enum ScopeNames { | |||
37 | function buildActorWithAvatarInclude () { | 24 | function buildActorWithAvatarInclude () { |
38 | return { | 25 | return { |
39 | attributes: [ 'preferredUsername' ], | 26 | attributes: [ 'preferredUsername' ], |
40 | model: () => ActorModel.unscoped(), | 27 | model: ActorModel.unscoped(), |
41 | required: true, | 28 | required: true, |
42 | include: [ | 29 | include: [ |
43 | { | 30 | { |
44 | attributes: [ 'filename' ], | 31 | attributes: [ 'filename' ], |
45 | model: () => AvatarModel.unscoped(), | 32 | model: AvatarModel.unscoped(), |
46 | required: false | 33 | required: false |
47 | }, | 34 | }, |
48 | { | 35 | { |
49 | attributes: [ 'host' ], | 36 | attributes: [ 'host' ], |
50 | model: () => ServerModel.unscoped(), | 37 | model: ServerModel.unscoped(), |
51 | required: false | 38 | required: false |
52 | } | 39 | } |
53 | ] | 40 | ] |
@@ -57,7 +44,7 @@ function buildActorWithAvatarInclude () { | |||
57 | function buildVideoInclude (required: boolean) { | 44 | function buildVideoInclude (required: boolean) { |
58 | return { | 45 | return { |
59 | attributes: [ 'id', 'uuid', 'name' ], | 46 | attributes: [ 'id', 'uuid', 'name' ], |
60 | model: () => VideoModel.unscoped(), | 47 | model: VideoModel.unscoped(), |
61 | required | 48 | required |
62 | } | 49 | } |
63 | } | 50 | } |
@@ -66,7 +53,7 @@ function buildChannelInclude (required: boolean, withActor = false) { | |||
66 | return { | 53 | return { |
67 | required, | 54 | required, |
68 | attributes: [ 'id', 'name' ], | 55 | attributes: [ 'id', 'name' ], |
69 | model: () => VideoChannelModel.unscoped(), | 56 | model: VideoChannelModel.unscoped(), |
70 | include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] | 57 | include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] |
71 | } | 58 | } |
72 | } | 59 | } |
@@ -75,12 +62,12 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
75 | return { | 62 | return { |
76 | required, | 63 | required, |
77 | attributes: [ 'id', 'name' ], | 64 | attributes: [ 'id', 'name' ], |
78 | model: () => AccountModel.unscoped(), | 65 | model: AccountModel.unscoped(), |
79 | include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] | 66 | include: withActor === true ? [ buildActorWithAvatarInclude() ] : [] |
80 | } | 67 | } |
81 | } | 68 | } |
82 | 69 | ||
83 | @Scopes({ | 70 | @Scopes(() => ({ |
84 | [ScopeNames.WITH_ALL]: { | 71 | [ScopeNames.WITH_ALL]: { |
85 | include: [ | 72 | include: [ |
86 | Object.assign(buildVideoInclude(false), { | 73 | Object.assign(buildVideoInclude(false), { |
@@ -89,7 +76,7 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
89 | 76 | ||
90 | { | 77 | { |
91 | attributes: [ 'id', 'originCommentId' ], | 78 | attributes: [ 'id', 'originCommentId' ], |
92 | model: () => VideoCommentModel.unscoped(), | 79 | model: VideoCommentModel.unscoped(), |
93 | required: false, | 80 | required: false, |
94 | include: [ | 81 | include: [ |
95 | buildAccountInclude(true, true), | 82 | buildAccountInclude(true, true), |
@@ -99,56 +86,56 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
99 | 86 | ||
100 | { | 87 | { |
101 | attributes: [ 'id' ], | 88 | attributes: [ 'id' ], |
102 | model: () => VideoAbuseModel.unscoped(), | 89 | model: VideoAbuseModel.unscoped(), |
103 | required: false, | 90 | required: false, |
104 | include: [ buildVideoInclude(true) ] | 91 | include: [ buildVideoInclude(true) ] |
105 | }, | 92 | }, |
106 | 93 | ||
107 | { | 94 | { |
108 | attributes: [ 'id' ], | 95 | attributes: [ 'id' ], |
109 | model: () => VideoBlacklistModel.unscoped(), | 96 | model: VideoBlacklistModel.unscoped(), |
110 | required: false, | 97 | required: false, |
111 | include: [ buildVideoInclude(true) ] | 98 | include: [ buildVideoInclude(true) ] |
112 | }, | 99 | }, |
113 | 100 | ||
114 | { | 101 | { |
115 | attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ], | 102 | attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ], |
116 | model: () => VideoImportModel.unscoped(), | 103 | model: VideoImportModel.unscoped(), |
117 | required: false, | 104 | required: false, |
118 | include: [ buildVideoInclude(false) ] | 105 | include: [ buildVideoInclude(false) ] |
119 | }, | 106 | }, |
120 | 107 | ||
121 | { | 108 | { |
122 | attributes: [ 'id' ], | 109 | attributes: [ 'id', 'state' ], |
123 | model: () => ActorFollowModel.unscoped(), | 110 | model: ActorFollowModel.unscoped(), |
124 | required: false, | 111 | required: false, |
125 | include: [ | 112 | include: [ |
126 | { | 113 | { |
127 | attributes: [ 'preferredUsername' ], | 114 | attributes: [ 'preferredUsername' ], |
128 | model: () => ActorModel.unscoped(), | 115 | model: ActorModel.unscoped(), |
129 | required: true, | 116 | required: true, |
130 | as: 'ActorFollower', | 117 | as: 'ActorFollower', |
131 | include: [ | 118 | include: [ |
132 | { | 119 | { |
133 | attributes: [ 'id', 'name' ], | 120 | attributes: [ 'id', 'name' ], |
134 | model: () => AccountModel.unscoped(), | 121 | model: AccountModel.unscoped(), |
135 | required: true | 122 | required: true |
136 | }, | 123 | }, |
137 | { | 124 | { |
138 | attributes: [ 'filename' ], | 125 | attributes: [ 'filename' ], |
139 | model: () => AvatarModel.unscoped(), | 126 | model: AvatarModel.unscoped(), |
140 | required: false | 127 | required: false |
141 | }, | 128 | }, |
142 | { | 129 | { |
143 | attributes: [ 'host' ], | 130 | attributes: [ 'host' ], |
144 | model: () => ServerModel.unscoped(), | 131 | model: ServerModel.unscoped(), |
145 | required: false | 132 | required: false |
146 | } | 133 | } |
147 | ] | 134 | ] |
148 | }, | 135 | }, |
149 | { | 136 | { |
150 | attributes: [ 'preferredUsername' ], | 137 | attributes: [ 'preferredUsername' ], |
151 | model: () => ActorModel.unscoped(), | 138 | model: ActorModel.unscoped(), |
152 | required: true, | 139 | required: true, |
153 | as: 'ActorFollowing', | 140 | as: 'ActorFollowing', |
154 | include: [ | 141 | include: [ |
@@ -162,7 +149,7 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
162 | buildAccountInclude(false, true) | 149 | buildAccountInclude(false, true) |
163 | ] | 150 | ] |
164 | } | 151 | } |
165 | }) | 152 | })) |
166 | @Table({ | 153 | @Table({ |
167 | tableName: 'userNotification', | 154 | tableName: 'userNotification', |
168 | indexes: [ | 155 | indexes: [ |
@@ -225,7 +212,7 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
225 | } | 212 | } |
226 | } | 213 | } |
227 | } | 214 | } |
228 | ] | 215 | ] as (ModelIndexesOptions & { where?: WhereOptions })[] |
229 | }) | 216 | }) |
230 | export class UserNotificationModel extends Model<UserNotificationModel> { | 217 | export class UserNotificationModel extends Model<UserNotificationModel> { |
231 | 218 | ||
@@ -344,7 +331,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
344 | ActorFollow: ActorFollowModel | 331 | ActorFollow: ActorFollowModel |
345 | 332 | ||
346 | static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { | 333 | static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { |
347 | const query: IFindOptions<UserNotificationModel> = { | 334 | const query: FindOptions = { |
348 | offset: start, | 335 | offset: start, |
349 | limit: count, | 336 | limit: count, |
350 | order: getSort(sort), | 337 | order: getSort(sort), |
@@ -370,7 +357,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
370 | where: { | 357 | where: { |
371 | userId, | 358 | userId, |
372 | id: { | 359 | id: { |
373 | [Op.any]: notificationIds | 360 | [Op.in]: notificationIds // FIXME: sequelize ANY seems broken |
374 | } | 361 | } |
375 | } | 362 | } |
376 | } | 363 | } |
@@ -418,6 +405,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
418 | 405 | ||
419 | const actorFollow = this.ActorFollow ? { | 406 | const actorFollow = this.ActorFollow ? { |
420 | id: this.ActorFollow.id, | 407 | id: this.ActorFollow.id, |
408 | state: this.ActorFollow.state, | ||
421 | follower: { | 409 | follower: { |
422 | id: this.ActorFollow.ActorFollower.Account.id, | 410 | id: this.ActorFollow.ActorFollower.Account.id, |
423 | displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), | 411 | displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), |
diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts index 15cb399c9..a862fc45f 100644 --- a/server/models/account/user-video-history.ts +++ b/server/models/account/user-video-history.ts | |||
@@ -67,7 +67,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> { | |||
67 | }) | 67 | }) |
68 | } | 68 | } |
69 | 69 | ||
70 | static removeHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) { | 70 | static removeUserHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) { |
71 | const query: DestroyOptions = { | 71 | const query: DestroyOptions = { |
72 | where: { | 72 | where: { |
73 | userId: user.id | 73 | userId: user.id |
@@ -76,11 +76,23 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> { | |||
76 | } | 76 | } |
77 | 77 | ||
78 | if (beforeDate) { | 78 | if (beforeDate) { |
79 | query.where.updatedAt = { | 79 | query.where['updatedAt'] = { |
80 | [Op.lt]: beforeDate | 80 | [Op.lt]: beforeDate |
81 | } | 81 | } |
82 | } | 82 | } |
83 | 83 | ||
84 | return UserVideoHistoryModel.destroy(query) | 84 | return UserVideoHistoryModel.destroy(query) |
85 | } | 85 | } |
86 | |||
87 | static removeOldHistory (beforeDate: string) { | ||
88 | const query: DestroyOptions = { | ||
89 | where: { | ||
90 | updatedAt: { | ||
91 | [Op.lt]: beforeDate | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | |||
96 | return UserVideoHistoryModel.destroy(query) | ||
97 | } | ||
86 | } | 98 | } |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 017a96657..4a9acd703 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { FindOptions, literal, Op, QueryTypes } from 'sequelize' |
2 | import { | 2 | import { |
3 | AfterDestroy, | 3 | AfterDestroy, |
4 | AfterUpdate, | 4 | AfterUpdate, |
@@ -22,6 +22,7 @@ import { | |||
22 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' | 22 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' |
23 | import { User, UserRole } from '../../../shared/models/users' | 23 | import { User, UserRole } from '../../../shared/models/users' |
24 | import { | 24 | import { |
25 | isUserAdminFlagsValid, | ||
25 | isUserAutoPlayVideoValid, | 26 | isUserAutoPlayVideoValid, |
26 | isUserBlockedReasonValid, | 27 | isUserBlockedReasonValid, |
27 | isUserBlockedValid, | 28 | isUserBlockedValid, |
@@ -42,45 +43,46 @@ import { VideoChannelModel } from '../video/video-channel' | |||
42 | import { AccountModel } from './account' | 43 | import { AccountModel } from './account' |
43 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' | 44 | import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' |
44 | import { values } from 'lodash' | 45 | import { values } from 'lodash' |
45 | import { NSFW_POLICY_TYPES } from '../../initializers' | 46 | import { NSFW_POLICY_TYPES } from '../../initializers/constants' |
46 | import { clearCacheByUserId } from '../../lib/oauth-model' | 47 | import { clearCacheByUserId } from '../../lib/oauth-model' |
47 | import { UserNotificationSettingModel } from './user-notification-setting' | 48 | import { UserNotificationSettingModel } from './user-notification-setting' |
48 | import { VideoModel } from '../video/video' | 49 | import { VideoModel } from '../video/video' |
49 | import { ActorModel } from '../activitypub/actor' | 50 | import { ActorModel } from '../activitypub/actor' |
50 | import { ActorFollowModel } from '../activitypub/actor-follow' | 51 | import { ActorFollowModel } from '../activitypub/actor-follow' |
51 | import { VideoImportModel } from '../video/video-import' | 52 | import { VideoImportModel } from '../video/video-import' |
53 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' | ||
52 | 54 | ||
53 | enum ScopeNames { | 55 | enum ScopeNames { |
54 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 56 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' |
55 | } | 57 | } |
56 | 58 | ||
57 | @DefaultScope({ | 59 | @DefaultScope(() => ({ |
58 | include: [ | 60 | include: [ |
59 | { | 61 | { |
60 | model: () => AccountModel, | 62 | model: AccountModel, |
61 | required: true | 63 | required: true |
62 | }, | 64 | }, |
63 | { | 65 | { |
64 | model: () => UserNotificationSettingModel, | 66 | model: UserNotificationSettingModel, |
65 | required: true | 67 | required: true |
66 | } | 68 | } |
67 | ] | 69 | ] |
68 | }) | 70 | })) |
69 | @Scopes({ | 71 | @Scopes(() => ({ |
70 | [ScopeNames.WITH_VIDEO_CHANNEL]: { | 72 | [ScopeNames.WITH_VIDEO_CHANNEL]: { |
71 | include: [ | 73 | include: [ |
72 | { | 74 | { |
73 | model: () => AccountModel, | 75 | model: AccountModel, |
74 | required: true, | 76 | required: true, |
75 | include: [ () => VideoChannelModel ] | 77 | include: [ VideoChannelModel ] |
76 | }, | 78 | }, |
77 | { | 79 | { |
78 | model: () => UserNotificationSettingModel, | 80 | model: UserNotificationSettingModel, |
79 | required: true | 81 | required: true |
80 | } | 82 | } |
81 | ] | 83 | ] |
82 | } | 84 | } |
83 | }) | 85 | })) |
84 | @Table({ | 86 | @Table({ |
85 | tableName: 'user', | 87 | tableName: 'user', |
86 | indexes: [ | 88 | indexes: [ |
@@ -113,13 +115,13 @@ export class UserModel extends Model<UserModel> { | |||
113 | 115 | ||
114 | @AllowNull(true) | 116 | @AllowNull(true) |
115 | @Default(null) | 117 | @Default(null) |
116 | @Is('UserEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean')) | 118 | @Is('UserEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true)) |
117 | @Column | 119 | @Column |
118 | emailVerified: boolean | 120 | emailVerified: boolean |
119 | 121 | ||
120 | @AllowNull(false) | 122 | @AllowNull(false) |
121 | @Is('UserNSFWPolicy', value => throwIfNotValid(value, isUserNSFWPolicyValid, 'NSFW policy')) | 123 | @Is('UserNSFWPolicy', value => throwIfNotValid(value, isUserNSFWPolicyValid, 'NSFW policy')) |
122 | @Column(DataType.ENUM(values(NSFW_POLICY_TYPES))) | 124 | @Column(DataType.ENUM(...values(NSFW_POLICY_TYPES))) |
123 | nsfwPolicy: NSFWPolicyType | 125 | nsfwPolicy: NSFWPolicyType |
124 | 126 | ||
125 | @AllowNull(false) | 127 | @AllowNull(false) |
@@ -141,6 +143,12 @@ export class UserModel extends Model<UserModel> { | |||
141 | autoPlayVideo: boolean | 143 | autoPlayVideo: boolean |
142 | 144 | ||
143 | @AllowNull(false) | 145 | @AllowNull(false) |
146 | @Default(UserAdminFlag.NONE) | ||
147 | @Is('UserAdminFlags', value => throwIfNotValid(value, isUserAdminFlagsValid, 'user admin flags')) | ||
148 | @Column | ||
149 | adminFlags?: UserAdminFlag | ||
150 | |||
151 | @AllowNull(false) | ||
144 | @Default(false) | 152 | @Default(false) |
145 | @Is('UserBlocked', value => throwIfNotValid(value, isUserBlockedValid, 'blocked boolean')) | 153 | @Is('UserBlocked', value => throwIfNotValid(value, isUserBlockedValid, 'blocked boolean')) |
146 | @Column | 154 | @Column |
@@ -148,7 +156,7 @@ export class UserModel extends Model<UserModel> { | |||
148 | 156 | ||
149 | @AllowNull(true) | 157 | @AllowNull(true) |
150 | @Default(null) | 158 | @Default(null) |
151 | @Is('UserBlockedReason', value => throwIfNotValid(value, isUserBlockedReasonValid, 'blocked reason')) | 159 | @Is('UserBlockedReason', value => throwIfNotValid(value, isUserBlockedReasonValid, 'blocked reason', true)) |
152 | @Column | 160 | @Column |
153 | blockedReason: string | 161 | blockedReason: string |
154 | 162 | ||
@@ -225,26 +233,26 @@ export class UserModel extends Model<UserModel> { | |||
225 | let where = undefined | 233 | let where = undefined |
226 | if (search) { | 234 | if (search) { |
227 | where = { | 235 | where = { |
228 | [Sequelize.Op.or]: [ | 236 | [Op.or]: [ |
229 | { | 237 | { |
230 | email: { | 238 | email: { |
231 | [Sequelize.Op.iLike]: '%' + search + '%' | 239 | [Op.iLike]: '%' + search + '%' |
232 | } | 240 | } |
233 | }, | 241 | }, |
234 | { | 242 | { |
235 | username: { | 243 | username: { |
236 | [ Sequelize.Op.iLike ]: '%' + search + '%' | 244 | [ Op.iLike ]: '%' + search + '%' |
237 | } | 245 | } |
238 | } | 246 | } |
239 | ] | 247 | ] |
240 | } | 248 | } |
241 | } | 249 | } |
242 | 250 | ||
243 | const query = { | 251 | const query: FindOptions = { |
244 | attributes: { | 252 | attributes: { |
245 | include: [ | 253 | include: [ |
246 | [ | 254 | [ |
247 | Sequelize.literal( | 255 | literal( |
248 | '(' + | 256 | '(' + |
249 | 'SELECT COALESCE(SUM("size"), 0) ' + | 257 | 'SELECT COALESCE(SUM("size"), 0) ' + |
250 | 'FROM (' + | 258 | 'FROM (' + |
@@ -257,7 +265,7 @@ export class UserModel extends Model<UserModel> { | |||
257 | ')' | 265 | ')' |
258 | ), | 266 | ), |
259 | 'videoQuotaUsed' | 267 | 'videoQuotaUsed' |
260 | ] as any // FIXME: typings | 268 | ] |
261 | ] | 269 | ] |
262 | }, | 270 | }, |
263 | offset: start, | 271 | offset: start, |
@@ -283,7 +291,7 @@ export class UserModel extends Model<UserModel> { | |||
283 | const query = { | 291 | const query = { |
284 | where: { | 292 | where: { |
285 | role: { | 293 | role: { |
286 | [Sequelize.Op.in]: roles | 294 | [Op.in]: roles |
287 | } | 295 | } |
288 | } | 296 | } |
289 | } | 297 | } |
@@ -341,7 +349,7 @@ export class UserModel extends Model<UserModel> { | |||
341 | } | 349 | } |
342 | 350 | ||
343 | static loadById (id: number) { | 351 | static loadById (id: number) { |
344 | return UserModel.findById(id) | 352 | return UserModel.findByPk(id) |
345 | } | 353 | } |
346 | 354 | ||
347 | static loadByUsername (username: string) { | 355 | static loadByUsername (username: string) { |
@@ -379,7 +387,7 @@ export class UserModel extends Model<UserModel> { | |||
379 | 387 | ||
380 | const query = { | 388 | const query = { |
381 | where: { | 389 | where: { |
382 | [ Sequelize.Op.or ]: [ { username }, { email } ] | 390 | [ Op.or ]: [ { username }, { email } ] |
383 | } | 391 | } |
384 | } | 392 | } |
385 | 393 | ||
@@ -502,7 +510,7 @@ export class UserModel extends Model<UserModel> { | |||
502 | const query = { | 510 | const query = { |
503 | where: { | 511 | where: { |
504 | username: { | 512 | username: { |
505 | [ Sequelize.Op.like ]: `%${search}%` | 513 | [ Op.like ]: `%${search}%` |
506 | } | 514 | } |
507 | }, | 515 | }, |
508 | limit: 10 | 516 | limit: 10 |
@@ -516,11 +524,15 @@ export class UserModel extends Model<UserModel> { | |||
516 | return hasUserRight(this.role, right) | 524 | return hasUserRight(this.role, right) |
517 | } | 525 | } |
518 | 526 | ||
527 | hasAdminFlag (flag: UserAdminFlag) { | ||
528 | return this.adminFlags & flag | ||
529 | } | ||
530 | |||
519 | isPasswordMatch (password: string) { | 531 | isPasswordMatch (password: string) { |
520 | return comparePassword(password, this.password) | 532 | return comparePassword(password, this.password) |
521 | } | 533 | } |
522 | 534 | ||
523 | toFormattedJSON (): User { | 535 | toFormattedJSON (parameters: { withAdminFlags?: boolean } = {}): User { |
524 | const videoQuotaUsed = this.get('videoQuotaUsed') | 536 | const videoQuotaUsed = this.get('videoQuotaUsed') |
525 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') | 537 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') |
526 | 538 | ||
@@ -544,13 +556,17 @@ export class UserModel extends Model<UserModel> { | |||
544 | notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined, | 556 | notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined, |
545 | videoChannels: [], | 557 | videoChannels: [], |
546 | videoQuotaUsed: videoQuotaUsed !== undefined | 558 | videoQuotaUsed: videoQuotaUsed !== undefined |
547 | ? parseInt(videoQuotaUsed, 10) | 559 | ? parseInt(videoQuotaUsed + '', 10) |
548 | : undefined, | 560 | : undefined, |
549 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined | 561 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined |
550 | ? parseInt(videoQuotaUsedDaily, 10) | 562 | ? parseInt(videoQuotaUsedDaily + '', 10) |
551 | : undefined | 563 | : undefined |
552 | } | 564 | } |
553 | 565 | ||
566 | if (parameters.withAdminFlags) { | ||
567 | Object.assign(json, { adminFlags: this.adminFlags }) | ||
568 | } | ||
569 | |||
554 | if (Array.isArray(this.Account.VideoChannels) === true) { | 570 | if (Array.isArray(this.Account.VideoChannels) === true) { |
555 | json.videoChannels = this.Account.VideoChannels | 571 | json.videoChannels = this.Account.VideoChannels |
556 | .map(c => c.toFormattedJSON()) | 572 | .map(c => c.toFormattedJSON()) |
@@ -575,15 +591,11 @@ export class UserModel extends Model<UserModel> { | |||
575 | 591 | ||
576 | const uploadedTotal = videoFile.size + totalBytes | 592 | const uploadedTotal = videoFile.size + totalBytes |
577 | const uploadedDaily = videoFile.size + totalBytesDaily | 593 | const uploadedDaily = videoFile.size + totalBytesDaily |
578 | if (this.videoQuotaDaily === -1) { | ||
579 | return uploadedTotal < this.videoQuota | ||
580 | } | ||
581 | if (this.videoQuota === -1) { | ||
582 | return uploadedDaily < this.videoQuotaDaily | ||
583 | } | ||
584 | 594 | ||
585 | return (uploadedTotal < this.videoQuota) && | 595 | if (this.videoQuotaDaily === -1) return uploadedTotal < this.videoQuota |
586 | (uploadedDaily < this.videoQuotaDaily) | 596 | if (this.videoQuota === -1) return uploadedDaily < this.videoQuotaDaily |
597 | |||
598 | return uploadedTotal < this.videoQuota && uploadedDaily < this.videoQuotaDaily | ||
587 | } | 599 | } |
588 | 600 | ||
589 | private static generateUserQuotaBaseSQL (where?: string) { | 601 | private static generateUserQuotaBaseSQL (where?: string) { |
@@ -603,10 +615,10 @@ export class UserModel extends Model<UserModel> { | |||
603 | private static getTotalRawQuery (query: string, userId: number) { | 615 | private static getTotalRawQuery (query: string, userId: number) { |
604 | const options = { | 616 | const options = { |
605 | bind: { userId }, | 617 | bind: { userId }, |
606 | type: Sequelize.QueryTypes.SELECT | 618 | type: QueryTypes.SELECT as QueryTypes.SELECT |
607 | } | 619 | } |
608 | 620 | ||
609 | return UserModel.sequelize.query(query, options) | 621 | return UserModel.sequelize.query<{ total: string }>(query, options) |
610 | .then(([ { total } ]) => { | 622 | .then(([ { total } ]) => { |
611 | if (total === null) return 0 | 623 | if (total === null) return 0 |
612 | 624 | ||
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 796e07a42..b0461b981 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { values } from 'lodash' | 2 | import { values } from 'lodash' |
3 | import * as Sequelize from 'sequelize' | ||
4 | import { | 3 | import { |
5 | AfterCreate, | 4 | AfterCreate, |
6 | AfterDestroy, | 5 | AfterDestroy, |
@@ -22,14 +21,13 @@ import { FollowState } from '../../../shared/models/actors' | |||
22 | import { ActorFollow } from '../../../shared/models/actors/follow.model' | 21 | import { ActorFollow } from '../../../shared/models/actors/follow.model' |
23 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
24 | import { getServerActor } from '../../helpers/utils' | 23 | import { getServerActor } from '../../helpers/utils' |
25 | import { ACTOR_FOLLOW_SCORE } from '../../initializers' | 24 | import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES } from '../../initializers/constants' |
26 | import { FOLLOW_STATES } from '../../initializers/constants' | ||
27 | import { ServerModel } from '../server/server' | 25 | import { ServerModel } from '../server/server' |
28 | import { getSort } from '../utils' | 26 | import { getSort } from '../utils' |
29 | import { ActorModel, unusedActorAttributesForAPI } from './actor' | 27 | import { ActorModel, unusedActorAttributesForAPI } from './actor' |
30 | import { VideoChannelModel } from '../video/video-channel' | 28 | import { VideoChannelModel } from '../video/video-channel' |
31 | import { IIncludeOptions } from '../../../node_modules/sequelize-typescript/lib/interfaces/IIncludeOptions' | ||
32 | import { AccountModel } from '../account/account' | 29 | import { AccountModel } from '../account/account' |
30 | import { IncludeOptions, Op, Transaction, QueryTypes } from 'sequelize' | ||
33 | 31 | ||
34 | @Table({ | 32 | @Table({ |
35 | tableName: 'actorFollow', | 33 | tableName: 'actorFollow', |
@@ -52,7 +50,7 @@ import { AccountModel } from '../account/account' | |||
52 | export class ActorFollowModel extends Model<ActorFollowModel> { | 50 | export class ActorFollowModel extends Model<ActorFollowModel> { |
53 | 51 | ||
54 | @AllowNull(false) | 52 | @AllowNull(false) |
55 | @Column(DataType.ENUM(values(FOLLOW_STATES))) | 53 | @Column(DataType.ENUM(...values(FOLLOW_STATES))) |
56 | state: FollowState | 54 | state: FollowState |
57 | 55 | ||
58 | @AllowNull(false) | 56 | @AllowNull(false) |
@@ -127,7 +125,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
127 | if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) | 125 | if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) |
128 | } | 126 | } |
129 | 127 | ||
130 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Sequelize.Transaction) { | 128 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction) { |
131 | const query = { | 129 | const query = { |
132 | where: { | 130 | where: { |
133 | actorId, | 131 | actorId, |
@@ -151,8 +149,8 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
151 | return ActorFollowModel.findOne(query) | 149 | return ActorFollowModel.findOne(query) |
152 | } | 150 | } |
153 | 151 | ||
154 | static loadByActorAndTargetNameAndHostForAPI (actorId: number, targetName: string, targetHost: string, t?: Sequelize.Transaction) { | 152 | static loadByActorAndTargetNameAndHostForAPI (actorId: number, targetName: string, targetHost: string, t?: Transaction) { |
155 | const actorFollowingPartInclude: IIncludeOptions = { | 153 | const actorFollowingPartInclude: IncludeOptions = { |
156 | model: ActorModel, | 154 | model: ActorModel, |
157 | required: true, | 155 | required: true, |
158 | as: 'ActorFollowing', | 156 | as: 'ActorFollowing', |
@@ -209,7 +207,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
209 | .map(t => { | 207 | .map(t => { |
210 | if (t.host) { | 208 | if (t.host) { |
211 | return { | 209 | return { |
212 | [ Sequelize.Op.and ]: [ | 210 | [ Op.and ]: [ |
213 | { | 211 | { |
214 | '$preferredUsername$': t.name | 212 | '$preferredUsername$': t.name |
215 | }, | 213 | }, |
@@ -221,7 +219,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
221 | } | 219 | } |
222 | 220 | ||
223 | return { | 221 | return { |
224 | [ Sequelize.Op.and ]: [ | 222 | [ Op.and ]: [ |
225 | { | 223 | { |
226 | '$preferredUsername$': t.name | 224 | '$preferredUsername$': t.name |
227 | }, | 225 | }, |
@@ -235,9 +233,9 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
235 | const query = { | 233 | const query = { |
236 | attributes: [], | 234 | attributes: [], |
237 | where: { | 235 | where: { |
238 | [ Sequelize.Op.and ]: [ | 236 | [ Op.and ]: [ |
239 | { | 237 | { |
240 | [ Sequelize.Op.or ]: whereTab | 238 | [ Op.or ]: whereTab |
241 | }, | 239 | }, |
242 | { | 240 | { |
243 | actorId | 241 | actorId |
@@ -289,7 +287,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
289 | required: true, | 287 | required: true, |
290 | where: search ? { | 288 | where: search ? { |
291 | host: { | 289 | host: { |
292 | [Sequelize.Op.iLike]: '%' + search + '%' | 290 | [Op.iLike]: '%' + search + '%' |
293 | } | 291 | } |
294 | } : undefined | 292 | } : undefined |
295 | } | 293 | } |
@@ -324,7 +322,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
324 | required: true, | 322 | required: true, |
325 | where: search ? { | 323 | where: search ? { |
326 | host: { | 324 | host: { |
327 | [ Sequelize.Op.iLike ]: '%' + search + '%' | 325 | [ Op.iLike ]: '%' + search + '%' |
328 | } | 326 | } |
329 | } : undefined | 327 | } : undefined |
330 | } | 328 | } |
@@ -407,11 +405,11 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
407 | }) | 405 | }) |
408 | } | 406 | } |
409 | 407 | ||
410 | static listAcceptedFollowerUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { | 408 | static listAcceptedFollowerUrlsForAP (actorIds: number[], t: Transaction, start?: number, count?: number) { |
411 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) | 409 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) |
412 | } | 410 | } |
413 | 411 | ||
414 | static listAcceptedFollowerSharedInboxUrls (actorIds: number[], t: Sequelize.Transaction) { | 412 | static listAcceptedFollowerSharedInboxUrls (actorIds: number[], t: Transaction) { |
415 | return ActorFollowModel.createListAcceptedFollowForApiQuery( | 413 | return ActorFollowModel.createListAcceptedFollowForApiQuery( |
416 | 'followers', | 414 | 'followers', |
417 | actorIds, | 415 | actorIds, |
@@ -423,7 +421,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
423 | ) | 421 | ) |
424 | } | 422 | } |
425 | 423 | ||
426 | static listAcceptedFollowingUrlsForApi (actorIds: number[], t: Sequelize.Transaction, start?: number, count?: number) { | 424 | static listAcceptedFollowingUrlsForApi (actorIds: number[], t: Transaction, start?: number, count?: number) { |
427 | return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count) | 425 | return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count) |
428 | } | 426 | } |
429 | 427 | ||
@@ -448,7 +446,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
448 | } | 446 | } |
449 | } | 447 | } |
450 | 448 | ||
451 | static updateFollowScore (inboxUrl: string, value: number, t?: Sequelize.Transaction) { | 449 | static updateFollowScore (inboxUrl: string, value: number, t?: Transaction) { |
452 | const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` + | 450 | const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` + |
453 | 'WHERE id IN (' + | 451 | 'WHERE id IN (' + |
454 | 'SELECT "actorFollow"."id" FROM "actorFollow" ' + | 452 | 'SELECT "actorFollow"."id" FROM "actorFollow" ' + |
@@ -457,7 +455,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
457 | ')' | 455 | ')' |
458 | 456 | ||
459 | const options = { | 457 | const options = { |
460 | type: Sequelize.QueryTypes.BULKUPDATE, | 458 | type: QueryTypes.BULKUPDATE, |
461 | transaction: t | 459 | transaction: t |
462 | } | 460 | } |
463 | 461 | ||
@@ -467,7 +465,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
467 | private static async createListAcceptedFollowForApiQuery ( | 465 | private static async createListAcceptedFollowForApiQuery ( |
468 | type: 'followers' | 'following', | 466 | type: 'followers' | 'following', |
469 | actorIds: number[], | 467 | actorIds: number[], |
470 | t: Sequelize.Transaction, | 468 | t: Transaction, |
471 | start?: number, | 469 | start?: number, |
472 | count?: number, | 470 | count?: number, |
473 | columnUrl = 'url', | 471 | columnUrl = 'url', |
@@ -503,7 +501,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
503 | 501 | ||
504 | const options = { | 502 | const options = { |
505 | bind: { actorIds }, | 503 | bind: { actorIds }, |
506 | type: Sequelize.QueryTypes.SELECT, | 504 | type: QueryTypes.SELECT, |
507 | transaction: t | 505 | transaction: t |
508 | } | 506 | } |
509 | tasks.push(ActorFollowModel.sequelize.query(query, options)) | 507 | tasks.push(ActorFollowModel.sequelize.query(query, options)) |
@@ -522,7 +520,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
522 | const query = { | 520 | const query = { |
523 | where: { | 521 | where: { |
524 | score: { | 522 | score: { |
525 | [Sequelize.Op.lte]: 0 | 523 | [Op.lte]: 0 |
526 | } | 524 | } |
527 | }, | 525 | }, |
528 | logging: false | 526 | logging: false |
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index dda57a8ba..4a466441c 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -30,11 +30,11 @@ import { | |||
30 | isActorPublicKeyValid | 30 | isActorPublicKeyValid |
31 | } from '../../helpers/custom-validators/activitypub/actor' | 31 | } from '../../helpers/custom-validators/activitypub/actor' |
32 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 32 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
33 | import { ACTIVITY_PUB, ACTIVITY_PUB_ACTOR_TYPES, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | 33 | import { ACTIVITY_PUB, ACTIVITY_PUB_ACTOR_TYPES, CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
34 | import { AccountModel } from '../account/account' | 34 | import { AccountModel } from '../account/account' |
35 | import { AvatarModel } from '../avatar/avatar' | 35 | import { AvatarModel } from '../avatar/avatar' |
36 | import { ServerModel } from '../server/server' | 36 | import { ServerModel } from '../server/server' |
37 | import { throwIfNotValid } from '../utils' | 37 | import { isOutdated, throwIfNotValid } from '../utils' |
38 | import { VideoChannelModel } from '../video/video-channel' | 38 | import { VideoChannelModel } from '../video/video-channel' |
39 | import { ActorFollowModel } from './actor-follow' | 39 | import { ActorFollowModel } from './actor-follow' |
40 | import { VideoModel } from '../video/video' | 40 | import { VideoModel } from '../video/video' |
@@ -56,46 +56,46 @@ export const unusedActorAttributesForAPI = [ | |||
56 | 'updatedAt' | 56 | 'updatedAt' |
57 | ] | 57 | ] |
58 | 58 | ||
59 | @DefaultScope({ | 59 | @DefaultScope(() => ({ |
60 | include: [ | 60 | include: [ |
61 | { | 61 | { |
62 | model: () => ServerModel, | 62 | model: ServerModel, |
63 | required: false | 63 | required: false |
64 | }, | 64 | }, |
65 | { | 65 | { |
66 | model: () => AvatarModel, | 66 | model: AvatarModel, |
67 | required: false | 67 | required: false |
68 | } | 68 | } |
69 | ] | 69 | ] |
70 | }) | 70 | })) |
71 | @Scopes({ | 71 | @Scopes(() => ({ |
72 | [ScopeNames.FULL]: { | 72 | [ScopeNames.FULL]: { |
73 | include: [ | 73 | include: [ |
74 | { | 74 | { |
75 | model: () => AccountModel.unscoped(), | 75 | model: AccountModel.unscoped(), |
76 | required: false | 76 | required: false |
77 | }, | 77 | }, |
78 | { | 78 | { |
79 | model: () => VideoChannelModel.unscoped(), | 79 | model: VideoChannelModel.unscoped(), |
80 | required: false, | 80 | required: false, |
81 | include: [ | 81 | include: [ |
82 | { | 82 | { |
83 | model: () => AccountModel, | 83 | model: AccountModel, |
84 | required: true | 84 | required: true |
85 | } | 85 | } |
86 | ] | 86 | ] |
87 | }, | 87 | }, |
88 | { | 88 | { |
89 | model: () => ServerModel, | 89 | model: ServerModel, |
90 | required: false | 90 | required: false |
91 | }, | 91 | }, |
92 | { | 92 | { |
93 | model: () => AvatarModel, | 93 | model: AvatarModel, |
94 | required: false | 94 | required: false |
95 | } | 95 | } |
96 | ] | 96 | ] |
97 | } | 97 | } |
98 | }) | 98 | })) |
99 | @Table({ | 99 | @Table({ |
100 | tableName: 'actor', | 100 | tableName: 'actor', |
101 | indexes: [ | 101 | indexes: [ |
@@ -131,7 +131,7 @@ export const unusedActorAttributesForAPI = [ | |||
131 | export class ActorModel extends Model<ActorModel> { | 131 | export class ActorModel extends Model<ActorModel> { |
132 | 132 | ||
133 | @AllowNull(false) | 133 | @AllowNull(false) |
134 | @Column(DataType.ENUM(values(ACTIVITY_PUB_ACTOR_TYPES))) | 134 | @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES))) |
135 | type: ActivityPubActorType | 135 | type: ActivityPubActorType |
136 | 136 | ||
137 | @AllowNull(false) | 137 | @AllowNull(false) |
@@ -151,12 +151,12 @@ export class ActorModel extends Model<ActorModel> { | |||
151 | url: string | 151 | url: string |
152 | 152 | ||
153 | @AllowNull(true) | 153 | @AllowNull(true) |
154 | @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key')) | 154 | @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key', true)) |
155 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY.max)) | 155 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY.max)) |
156 | publicKey: string | 156 | publicKey: string |
157 | 157 | ||
158 | @AllowNull(true) | 158 | @AllowNull(true) |
159 | @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key')) | 159 | @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key', true)) |
160 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY.max)) | 160 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY.max)) |
161 | privateKey: string | 161 | privateKey: string |
162 | 162 | ||
@@ -265,7 +265,7 @@ export class ActorModel extends Model<ActorModel> { | |||
265 | VideoChannel: VideoChannelModel | 265 | VideoChannel: VideoChannelModel |
266 | 266 | ||
267 | static load (id: number) { | 267 | static load (id: number) { |
268 | return ActorModel.unscoped().findById(id) | 268 | return ActorModel.unscoped().findByPk(id) |
269 | } | 269 | } |
270 | 270 | ||
271 | static loadAccountActorByVideoId (videoId: number, transaction: Sequelize.Transaction) { | 271 | static loadAccountActorByVideoId (videoId: number, transaction: Sequelize.Transaction) { |
@@ -280,14 +280,16 @@ export class ActorModel extends Model<ActorModel> { | |||
280 | attributes: [ 'id' ], | 280 | attributes: [ 'id' ], |
281 | model: VideoChannelModel.unscoped(), | 281 | model: VideoChannelModel.unscoped(), |
282 | required: true, | 282 | required: true, |
283 | include: { | 283 | include: [ |
284 | attributes: [ 'id' ], | 284 | { |
285 | model: VideoModel.unscoped(), | 285 | attributes: [ 'id' ], |
286 | required: true, | 286 | model: VideoModel.unscoped(), |
287 | where: { | 287 | required: true, |
288 | id: videoId | 288 | where: { |
289 | id: videoId | ||
290 | } | ||
289 | } | 291 | } |
290 | } | 292 | ] |
291 | } | 293 | } |
292 | ] | 294 | ] |
293 | } | 295 | } |
@@ -295,7 +297,7 @@ export class ActorModel extends Model<ActorModel> { | |||
295 | transaction | 297 | transaction |
296 | } | 298 | } |
297 | 299 | ||
298 | return ActorModel.unscoped().findOne(query as any) // FIXME: typings | 300 | return ActorModel.unscoped().findOne(query) |
299 | } | 301 | } |
300 | 302 | ||
301 | static isActorUrlExist (url: string) { | 303 | static isActorUrlExist (url: string) { |
@@ -389,8 +391,7 @@ export class ActorModel extends Model<ActorModel> { | |||
389 | } | 391 | } |
390 | 392 | ||
391 | static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) { | 393 | static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) { |
392 | // FIXME: typings | 394 | return ActorModel.increment(column, { |
393 | return (ActorModel as any).increment(column, { | ||
394 | by, | 395 | by, |
395 | where: { | 396 | where: { |
396 | id | 397 | id |
@@ -444,6 +445,7 @@ export class ActorModel extends Model<ActorModel> { | |||
444 | id: this.url, | 445 | id: this.url, |
445 | following: this.getFollowingUrl(), | 446 | following: this.getFollowingUrl(), |
446 | followers: this.getFollowersUrl(), | 447 | followers: this.getFollowersUrl(), |
448 | playlists: this.getPlaylistsUrl(), | ||
447 | inbox: this.inboxUrl, | 449 | inbox: this.inboxUrl, |
448 | outbox: this.outboxUrl, | 450 | outbox: this.outboxUrl, |
449 | preferredUsername: this.preferredUsername, | 451 | preferredUsername: this.preferredUsername, |
@@ -494,6 +496,10 @@ export class ActorModel extends Model<ActorModel> { | |||
494 | return this.url + '/followers' | 496 | return this.url + '/followers' |
495 | } | 497 | } |
496 | 498 | ||
499 | getPlaylistsUrl () { | ||
500 | return this.url + '/playlists' | ||
501 | } | ||
502 | |||
497 | getPublicKeyUrl () { | 503 | getPublicKeyUrl () { |
498 | return this.url + '#main-key' | 504 | return this.url + '#main-key' |
499 | } | 505 | } |
@@ -511,7 +517,7 @@ export class ActorModel extends Model<ActorModel> { | |||
511 | } | 517 | } |
512 | 518 | ||
513 | getHost () { | 519 | getHost () { |
514 | return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST | 520 | return this.Server ? this.Server.host : WEBSERVER.HOST |
515 | } | 521 | } |
516 | 522 | ||
517 | getRedundancyAllowed () { | 523 | getRedundancyAllowed () { |
@@ -521,17 +527,12 @@ export class ActorModel extends Model<ActorModel> { | |||
521 | getAvatarUrl () { | 527 | getAvatarUrl () { |
522 | if (!this.avatarId) return undefined | 528 | if (!this.avatarId) return undefined |
523 | 529 | ||
524 | return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath() | 530 | return WEBSERVER.URL + this.Avatar.getWebserverPath() |
525 | } | 531 | } |
526 | 532 | ||
527 | isOutdated () { | 533 | isOutdated () { |
528 | if (this.isOwned()) return false | 534 | if (this.isOwned()) return false |
529 | 535 | ||
530 | const now = Date.now() | 536 | return isOutdated(this, ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL) |
531 | const createdAtTime = this.createdAt.getTime() | ||
532 | const updatedAtTime = this.updatedAt.getTime() | ||
533 | |||
534 | return (now - createdAtTime) > ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL && | ||
535 | (now - updatedAtTime) > ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL | ||
536 | } | 537 | } |
537 | } | 538 | } |
diff --git a/server/models/application/application.ts b/server/models/application/application.ts index 854a5fb36..81320b9af 100644 --- a/server/models/application/application.ts +++ b/server/models/application/application.ts | |||
@@ -1,16 +1,17 @@ | |||
1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' | 1 | import { AllowNull, Column, Default, DefaultScope, HasOne, IsInt, Model, Table } from 'sequelize-typescript' |
2 | import { AccountModel } from '../account/account' | 2 | import { AccountModel } from '../account/account' |
3 | 3 | ||
4 | @DefaultScope({ | 4 | @DefaultScope(() => ({ |
5 | include: [ | 5 | include: [ |
6 | { | 6 | { |
7 | model: () => AccountModel, | 7 | model: AccountModel, |
8 | required: true | 8 | required: true |
9 | } | 9 | } |
10 | ] | 10 | ] |
11 | }) | 11 | })) |
12 | @Table({ | 12 | @Table({ |
13 | tableName: 'application' | 13 | tableName: 'application', |
14 | timestamps: false | ||
14 | }) | 15 | }) |
15 | export class ApplicationModel extends Model<ApplicationModel> { | 16 | export class ApplicationModel extends Model<ApplicationModel> { |
16 | 17 | ||
diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts index 303aebcc2..aaf1b8bd9 100644 --- a/server/models/avatar/avatar.ts +++ b/server/models/avatar/avatar.ts | |||
@@ -1,9 +1,10 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { AfterDestroy, AllowNull, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AfterDestroy, AllowNull, Column, CreatedAt, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { Avatar } from '../../../shared/models/avatars/avatar.model' | 3 | import { Avatar } from '../../../shared/models/avatars/avatar.model' |
4 | import { CONFIG, STATIC_PATHS } from '../../initializers' | 4 | import { STATIC_PATHS } from '../../initializers/constants' |
5 | import { logger } from '../../helpers/logger' | 5 | import { logger } from '../../helpers/logger' |
6 | import { remove } from 'fs-extra' | 6 | import { remove } from 'fs-extra' |
7 | import { CONFIG } from '../../initializers/config' | ||
7 | 8 | ||
8 | @Table({ | 9 | @Table({ |
9 | tableName: 'avatar' | 10 | tableName: 'avatar' |
diff --git a/server/models/migrations.ts b/server/models/migrations.ts index 24badb166..6c11332a1 100644 --- a/server/models/migrations.ts +++ b/server/models/migrations.ts | |||
@@ -1,24 +1,24 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { ModelAttributeColumnOptions } from 'sequelize' |
2 | 2 | ||
3 | declare namespace Migration { | 3 | declare namespace Migration { |
4 | interface Boolean extends Sequelize.DefineAttributeColumnOptions { | 4 | interface Boolean extends ModelAttributeColumnOptions { |
5 | defaultValue: boolean | null | 5 | defaultValue: boolean | null |
6 | } | 6 | } |
7 | 7 | ||
8 | interface String extends Sequelize.DefineAttributeColumnOptions { | 8 | interface String extends ModelAttributeColumnOptions { |
9 | defaultValue: string | null | 9 | defaultValue: string | null |
10 | } | 10 | } |
11 | 11 | ||
12 | interface Integer extends Sequelize.DefineAttributeColumnOptions { | 12 | interface Integer extends ModelAttributeColumnOptions { |
13 | defaultValue: number | null | 13 | defaultValue: number | null |
14 | } | 14 | } |
15 | 15 | ||
16 | interface BigInteger extends Sequelize.DefineAttributeColumnOptions { | 16 | interface BigInteger extends ModelAttributeColumnOptions { |
17 | defaultValue: Sequelize.DataTypeBigInt | number | null | 17 | defaultValue: number | null |
18 | } | 18 | } |
19 | 19 | ||
20 | interface UUID extends Sequelize.DefineAttributeColumnOptions { | 20 | interface UUID extends ModelAttributeColumnOptions { |
21 | defaultValue: Sequelize.DataTypeUUIDv4 | null | 21 | defaultValue: null |
22 | } | 22 | } |
23 | } | 23 | } |
24 | 24 | ||
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index 08d892da4..903d551df 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -34,21 +34,21 @@ enum ScopeNames { | |||
34 | WITH_USER = 'WITH_USER' | 34 | WITH_USER = 'WITH_USER' |
35 | } | 35 | } |
36 | 36 | ||
37 | @Scopes({ | 37 | @Scopes(() => ({ |
38 | [ScopeNames.WITH_USER]: { | 38 | [ScopeNames.WITH_USER]: { |
39 | include: [ | 39 | include: [ |
40 | { | 40 | { |
41 | model: () => UserModel.unscoped(), | 41 | model: UserModel.unscoped(), |
42 | required: true, | 42 | required: true, |
43 | include: [ | 43 | include: [ |
44 | { | 44 | { |
45 | attributes: [ 'id' ], | 45 | attributes: [ 'id' ], |
46 | model: () => AccountModel.unscoped(), | 46 | model: AccountModel.unscoped(), |
47 | required: true, | 47 | required: true, |
48 | include: [ | 48 | include: [ |
49 | { | 49 | { |
50 | attributes: [ 'id', 'url' ], | 50 | attributes: [ 'id', 'url' ], |
51 | model: () => ActorModel.unscoped(), | 51 | model: ActorModel.unscoped(), |
52 | required: true | 52 | required: true |
53 | } | 53 | } |
54 | ] | 54 | ] |
@@ -57,7 +57,7 @@ enum ScopeNames { | |||
57 | } | 57 | } |
58 | ] | 58 | ] |
59 | } | 59 | } |
60 | }) | 60 | })) |
61 | @Table({ | 61 | @Table({ |
62 | tableName: 'oAuthToken', | 62 | tableName: 'oAuthToken', |
63 | indexes: [ | 63 | indexes: [ |
@@ -167,11 +167,13 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
167 | } | 167 | } |
168 | } | 168 | } |
169 | 169 | ||
170 | return OAuthTokenModel.scope(ScopeNames.WITH_USER).findOne(query).then(token => { | 170 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) |
171 | if (token) token['user'] = token.User | 171 | .findOne(query) |
172 | .then(token => { | ||
173 | if (token) token[ 'user' ] = token.User | ||
172 | 174 | ||
173 | return token | 175 | return token |
174 | }) | 176 | }) |
175 | } | 177 | } |
176 | 178 | ||
177 | static getByRefreshTokenAndPopulateUser (refreshToken: string) { | 179 | static getByRefreshTokenAndPopulateUser (refreshToken: string) { |
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 8f2ef2d9a..eb2222256 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -13,9 +13,9 @@ import { | |||
13 | UpdatedAt | 13 | UpdatedAt |
14 | } from 'sequelize-typescript' | 14 | } from 'sequelize-typescript' |
15 | import { ActorModel } from '../activitypub/actor' | 15 | import { ActorModel } from '../activitypub/actor' |
16 | import { getVideoSort, throwIfNotValid } from '../utils' | 16 | import { getVideoSort, parseAggregateResult, throwIfNotValid } from '../utils' |
17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 17 | import { isActivityPubUrlValid, isUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
18 | import { CONFIG, CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers' | 18 | import { CONSTRAINTS_FIELDS, MIMETYPES } from '../../initializers/constants' |
19 | import { VideoFileModel } from '../video/video-file' | 19 | import { VideoFileModel } from '../video/video-file' |
20 | import { getServerActor } from '../../helpers/utils' | 20 | import { getServerActor } from '../../helpers/utils' |
21 | import { VideoModel } from '../video/video' | 21 | import { VideoModel } from '../video/video' |
@@ -27,28 +27,40 @@ import { ServerModel } from '../server/server' | |||
27 | import { sample } from 'lodash' | 27 | import { sample } from 'lodash' |
28 | import { isTestInstance } from '../../helpers/core-utils' | 28 | import { isTestInstance } from '../../helpers/core-utils' |
29 | import * as Bluebird from 'bluebird' | 29 | import * as Bluebird from 'bluebird' |
30 | import * as Sequelize from 'sequelize' | 30 | import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' |
31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' | ||
32 | import { CONFIG } from '../../initializers/config' | ||
31 | 33 | ||
32 | export enum ScopeNames { | 34 | export enum ScopeNames { |
33 | WITH_VIDEO = 'WITH_VIDEO' | 35 | WITH_VIDEO = 'WITH_VIDEO' |
34 | } | 36 | } |
35 | 37 | ||
36 | @Scopes({ | 38 | @Scopes(() => ({ |
37 | [ ScopeNames.WITH_VIDEO ]: { | 39 | [ ScopeNames.WITH_VIDEO ]: { |
38 | include: [ | 40 | include: [ |
39 | { | 41 | { |
40 | model: () => VideoFileModel, | 42 | model: VideoFileModel, |
41 | required: true, | 43 | required: false, |
42 | include: [ | 44 | include: [ |
43 | { | 45 | { |
44 | model: () => VideoModel, | 46 | model: VideoModel, |
47 | required: true | ||
48 | } | ||
49 | ] | ||
50 | }, | ||
51 | { | ||
52 | model: VideoStreamingPlaylistModel, | ||
53 | required: false, | ||
54 | include: [ | ||
55 | { | ||
56 | model: VideoModel, | ||
45 | required: true | 57 | required: true |
46 | } | 58 | } |
47 | ] | 59 | ] |
48 | } | 60 | } |
49 | ] | 61 | ] |
50 | } | 62 | } |
51 | }) | 63 | })) |
52 | 64 | ||
53 | @Table({ | 65 | @Table({ |
54 | tableName: 'videoRedundancy', | 66 | tableName: 'videoRedundancy', |
@@ -97,12 +109,24 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
97 | 109 | ||
98 | @BelongsTo(() => VideoFileModel, { | 110 | @BelongsTo(() => VideoFileModel, { |
99 | foreignKey: { | 111 | foreignKey: { |
100 | allowNull: false | 112 | allowNull: true |
101 | }, | 113 | }, |
102 | onDelete: 'cascade' | 114 | onDelete: 'cascade' |
103 | }) | 115 | }) |
104 | VideoFile: VideoFileModel | 116 | VideoFile: VideoFileModel |
105 | 117 | ||
118 | @ForeignKey(() => VideoStreamingPlaylistModel) | ||
119 | @Column | ||
120 | videoStreamingPlaylistId: number | ||
121 | |||
122 | @BelongsTo(() => VideoStreamingPlaylistModel, { | ||
123 | foreignKey: { | ||
124 | allowNull: true | ||
125 | }, | ||
126 | onDelete: 'cascade' | ||
127 | }) | ||
128 | VideoStreamingPlaylist: VideoStreamingPlaylistModel | ||
129 | |||
106 | @ForeignKey(() => ActorModel) | 130 | @ForeignKey(() => ActorModel) |
107 | @Column | 131 | @Column |
108 | actorId: number | 132 | actorId: number |
@@ -119,13 +143,25 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
119 | static async removeFile (instance: VideoRedundancyModel) { | 143 | static async removeFile (instance: VideoRedundancyModel) { |
120 | if (!instance.isOwned()) return | 144 | if (!instance.isOwned()) return |
121 | 145 | ||
122 | const videoFile = await VideoFileModel.loadWithVideo(instance.videoFileId) | 146 | if (instance.videoFileId) { |
147 | const videoFile = await VideoFileModel.loadWithVideo(instance.videoFileId) | ||
123 | 148 | ||
124 | const logIdentifier = `${videoFile.Video.uuid}-${videoFile.resolution}` | 149 | const logIdentifier = `${videoFile.Video.uuid}-${videoFile.resolution}` |
125 | logger.info('Removing duplicated video file %s.', logIdentifier) | 150 | logger.info('Removing duplicated video file %s.', logIdentifier) |
126 | 151 | ||
127 | videoFile.Video.removeFile(videoFile, true) | 152 | videoFile.Video.removeFile(videoFile, true) |
128 | .catch(err => logger.error('Cannot delete %s files.', logIdentifier, { err })) | 153 | .catch(err => logger.error('Cannot delete %s files.', logIdentifier, { err })) |
154 | } | ||
155 | |||
156 | if (instance.videoStreamingPlaylistId) { | ||
157 | const videoStreamingPlaylist = await VideoStreamingPlaylistModel.loadWithVideo(instance.videoStreamingPlaylistId) | ||
158 | |||
159 | const videoUUID = videoStreamingPlaylist.Video.uuid | ||
160 | logger.info('Removing duplicated video streaming playlist %s.', videoUUID) | ||
161 | |||
162 | videoStreamingPlaylist.Video.removeStreamingPlaylist(true) | ||
163 | .catch(err => logger.error('Cannot delete video streaming playlist files of %s.', videoUUID, { err })) | ||
164 | } | ||
129 | 165 | ||
130 | return undefined | 166 | return undefined |
131 | } | 167 | } |
@@ -143,7 +179,20 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
143 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | 179 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) |
144 | } | 180 | } |
145 | 181 | ||
146 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | 182 | static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number) { |
183 | const actor = await getServerActor() | ||
184 | |||
185 | const query = { | ||
186 | where: { | ||
187 | actorId: actor.id, | ||
188 | videoStreamingPlaylistId | ||
189 | } | ||
190 | } | ||
191 | |||
192 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | ||
193 | } | ||
194 | |||
195 | static loadByUrl (url: string, transaction?: Transaction) { | ||
147 | const query = { | 196 | const query = { |
148 | where: { | 197 | where: { |
149 | url | 198 | url |
@@ -191,7 +240,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
191 | const ids = rows.map(r => r.id) | 240 | const ids = rows.map(r => r.id) |
192 | const id = sample(ids) | 241 | const id = sample(ids) |
193 | 242 | ||
194 | return VideoModel.loadWithFile(id, undefined, !isTestInstance()) | 243 | return VideoModel.loadWithFiles(id, undefined, !isTestInstance()) |
195 | } | 244 | } |
196 | 245 | ||
197 | static async findMostViewToDuplicate (randomizedFactor: number) { | 246 | static async findMostViewToDuplicate (randomizedFactor: number) { |
@@ -243,7 +292,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
243 | where: { | 292 | where: { |
244 | privacy: VideoPrivacy.PUBLIC, | 293 | privacy: VideoPrivacy.PUBLIC, |
245 | views: { | 294 | views: { |
246 | [ Sequelize.Op.gte ]: minViews | 295 | [ Op.gte ]: minViews |
247 | } | 296 | } |
248 | }, | 297 | }, |
249 | include: [ | 298 | include: [ |
@@ -266,7 +315,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
266 | actorId: actor.id, | 315 | actorId: actor.id, |
267 | strategy, | 316 | strategy, |
268 | createdAt: { | 317 | createdAt: { |
269 | [ Sequelize.Op.lt ]: expiredDate | 318 | [ Op.lt ]: expiredDate |
270 | } | 319 | } |
271 | } | 320 | } |
272 | } | 321 | } |
@@ -277,7 +326,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
277 | static async getTotalDuplicated (strategy: VideoRedundancyStrategy) { | 326 | static async getTotalDuplicated (strategy: VideoRedundancyStrategy) { |
278 | const actor = await getServerActor() | 327 | const actor = await getServerActor() |
279 | 328 | ||
280 | const options = { | 329 | const query: FindOptions = { |
281 | include: [ | 330 | include: [ |
282 | { | 331 | { |
283 | attributes: [], | 332 | attributes: [], |
@@ -291,12 +340,8 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
291 | ] | 340 | ] |
292 | } | 341 | } |
293 | 342 | ||
294 | return VideoFileModel.sum('size', options as any) // FIXME: typings | 343 | return VideoFileModel.aggregate('size', 'SUM', query) |
295 | .then(v => { | 344 | .then(result => parseAggregateResult(result)) |
296 | if (!v || isNaN(v)) return 0 | ||
297 | |||
298 | return v | ||
299 | }) | ||
300 | } | 345 | } |
301 | 346 | ||
302 | static async listLocalExpired () { | 347 | static async listLocalExpired () { |
@@ -306,7 +351,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
306 | where: { | 351 | where: { |
307 | actorId: actor.id, | 352 | actorId: actor.id, |
308 | expiresOn: { | 353 | expiresOn: { |
309 | [ Sequelize.Op.lt ]: new Date() | 354 | [ Op.lt ]: new Date() |
310 | } | 355 | } |
311 | } | 356 | } |
312 | } | 357 | } |
@@ -320,10 +365,10 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
320 | const query = { | 365 | const query = { |
321 | where: { | 366 | where: { |
322 | actorId: { | 367 | actorId: { |
323 | [Sequelize.Op.ne]: actor.id | 368 | [Op.ne]: actor.id |
324 | }, | 369 | }, |
325 | expiresOn: { | 370 | expiresOn: { |
326 | [ Sequelize.Op.lt ]: new Date() | 371 | [ Op.lt ]: new Date() |
327 | } | 372 | } |
328 | } | 373 | } |
329 | } | 374 | } |
@@ -333,40 +378,44 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
333 | 378 | ||
334 | static async listLocalOfServer (serverId: number) { | 379 | static async listLocalOfServer (serverId: number) { |
335 | const actor = await getServerActor() | 380 | const actor = await getServerActor() |
336 | 381 | const buildVideoInclude = () => ({ | |
337 | const query = { | 382 | model: VideoModel, |
338 | where: { | 383 | required: true, |
339 | actorId: actor.id | ||
340 | }, | ||
341 | include: [ | 384 | include: [ |
342 | { | 385 | { |
343 | model: VideoFileModel, | 386 | attributes: [], |
387 | model: VideoChannelModel.unscoped(), | ||
344 | required: true, | 388 | required: true, |
345 | include: [ | 389 | include: [ |
346 | { | 390 | { |
347 | model: VideoModel, | 391 | attributes: [], |
392 | model: ActorModel.unscoped(), | ||
348 | required: true, | 393 | required: true, |
349 | include: [ | 394 | where: { |
350 | { | 395 | serverId |
351 | attributes: [], | 396 | } |
352 | model: VideoChannelModel.unscoped(), | ||
353 | required: true, | ||
354 | include: [ | ||
355 | { | ||
356 | attributes: [], | ||
357 | model: ActorModel.unscoped(), | ||
358 | required: true, | ||
359 | where: { | ||
360 | serverId | ||
361 | } | ||
362 | } | ||
363 | ] | ||
364 | } | ||
365 | ] | ||
366 | } | 397 | } |
367 | ] | 398 | ] |
368 | } | 399 | } |
369 | ] | 400 | ] |
401 | }) | ||
402 | |||
403 | const query = { | ||
404 | where: { | ||
405 | actorId: actor.id | ||
406 | }, | ||
407 | include: [ | ||
408 | { | ||
409 | model: VideoFileModel, | ||
410 | required: false, | ||
411 | include: [ buildVideoInclude() ] | ||
412 | }, | ||
413 | { | ||
414 | model: VideoStreamingPlaylistModel, | ||
415 | required: false, | ||
416 | include: [ buildVideoInclude() ] | ||
417 | } | ||
418 | ] | ||
370 | } | 419 | } |
371 | 420 | ||
372 | return VideoRedundancyModel.findAll(query) | 421 | return VideoRedundancyModel.findAll(query) |
@@ -375,12 +424,12 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
375 | static async getStats (strategy: VideoRedundancyStrategy) { | 424 | static async getStats (strategy: VideoRedundancyStrategy) { |
376 | const actor = await getServerActor() | 425 | const actor = await getServerActor() |
377 | 426 | ||
378 | const query = { | 427 | const query: FindOptions = { |
379 | raw: true, | 428 | raw: true, |
380 | attributes: [ | 429 | attributes: [ |
381 | [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoFile.size')), '0'), 'totalUsed' ], | 430 | [ fn('COALESCE', fn('SUM', col('VideoFile.size')), '0'), 'totalUsed' ], |
382 | [ Sequelize.fn('COUNT', Sequelize.fn('DISTINCT', Sequelize.col('videoId'))), 'totalVideos' ], | 431 | [ fn('COUNT', fn('DISTINCT', col('videoId'))), 'totalVideos' ], |
383 | [ Sequelize.fn('COUNT', Sequelize.col('videoFileId')), 'totalVideoFiles' ] | 432 | [ fn('COUNT', col('videoFileId')), 'totalVideoFiles' ] |
384 | ], | 433 | ], |
385 | where: { | 434 | where: { |
386 | strategy, | 435 | strategy, |
@@ -395,19 +444,40 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
395 | ] | 444 | ] |
396 | } | 445 | } |
397 | 446 | ||
398 | return VideoRedundancyModel.findOne(query as any) // FIXME: typings | 447 | return VideoRedundancyModel.findOne(query) |
399 | .then((r: any) => ({ | 448 | .then((r: any) => ({ |
400 | totalUsed: parseInt(r.totalUsed.toString(), 10), | 449 | totalUsed: parseAggregateResult(r.totalUsed), |
401 | totalVideos: r.totalVideos, | 450 | totalVideos: r.totalVideos, |
402 | totalVideoFiles: r.totalVideoFiles | 451 | totalVideoFiles: r.totalVideoFiles |
403 | })) | 452 | })) |
404 | } | 453 | } |
405 | 454 | ||
455 | getVideo () { | ||
456 | if (this.VideoFile) return this.VideoFile.Video | ||
457 | |||
458 | return this.VideoStreamingPlaylist.Video | ||
459 | } | ||
460 | |||
406 | isOwned () { | 461 | isOwned () { |
407 | return !!this.strategy | 462 | return !!this.strategy |
408 | } | 463 | } |
409 | 464 | ||
410 | toActivityPubObject (): CacheFileObject { | 465 | toActivityPubObject (): CacheFileObject { |
466 | if (this.VideoStreamingPlaylist) { | ||
467 | return { | ||
468 | id: this.url, | ||
469 | type: 'CacheFile' as 'CacheFile', | ||
470 | object: this.VideoStreamingPlaylist.Video.url, | ||
471 | expires: this.expiresOn.toISOString(), | ||
472 | url: { | ||
473 | type: 'Link', | ||
474 | mimeType: 'application/x-mpegURL', | ||
475 | mediaType: 'application/x-mpegURL', | ||
476 | href: this.fileUrl | ||
477 | } | ||
478 | } | ||
479 | } | ||
480 | |||
411 | return { | 481 | return { |
412 | id: this.url, | 482 | id: this.url, |
413 | type: 'CacheFile' as 'CacheFile', | 483 | type: 'CacheFile' as 'CacheFile', |
@@ -429,9 +499,9 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
429 | private static async buildVideoFileForDuplication () { | 499 | private static async buildVideoFileForDuplication () { |
430 | const actor = await getServerActor() | 500 | const actor = await getServerActor() |
431 | 501 | ||
432 | const notIn = Sequelize.literal( | 502 | const notIn = literal( |
433 | '(' + | 503 | '(' + |
434 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id}` + | 504 | `SELECT "videoFileId" FROM "videoRedundancy" WHERE "actorId" = ${actor.id} AND "videoFileId" IS NOT NULL` + |
435 | ')' | 505 | ')' |
436 | ) | 506 | ) |
437 | 507 | ||
@@ -441,7 +511,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
441 | required: true, | 511 | required: true, |
442 | where: { | 512 | where: { |
443 | id: { | 513 | id: { |
444 | [ Sequelize.Op.notIn ]: notIn | 514 | [ Op.notIn ]: notIn |
445 | } | 515 | } |
446 | } | 516 | } |
447 | } | 517 | } |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 450f27152..92c01f642 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -9,11 +9,11 @@ enum ScopeNames { | |||
9 | WITH_SERVER = 'WITH_SERVER' | 9 | WITH_SERVER = 'WITH_SERVER' |
10 | } | 10 | } |
11 | 11 | ||
12 | @Scopes({ | 12 | @Scopes(() => ({ |
13 | [ScopeNames.WITH_ACCOUNT]: { | 13 | [ScopeNames.WITH_ACCOUNT]: { |
14 | include: [ | 14 | include: [ |
15 | { | 15 | { |
16 | model: () => AccountModel, | 16 | model: AccountModel, |
17 | required: true | 17 | required: true |
18 | } | 18 | } |
19 | ] | 19 | ] |
@@ -21,12 +21,12 @@ enum ScopeNames { | |||
21 | [ScopeNames.WITH_SERVER]: { | 21 | [ScopeNames.WITH_SERVER]: { |
22 | include: [ | 22 | include: [ |
23 | { | 23 | { |
24 | model: () => ServerModel, | 24 | model: ServerModel, |
25 | required: true | 25 | required: true |
26 | } | 26 | } |
27 | ] | 27 | ] |
28 | } | 28 | } |
29 | }) | 29 | })) |
30 | 30 | ||
31 | @Table({ | 31 | @Table({ |
32 | tableName: 'serverBlocklist', | 32 | tableName: 'serverBlocklist', |
diff --git a/server/models/utils.ts b/server/models/utils.ts index 5b4093aec..2b172f608 100644 --- a/server/models/utils.ts +++ b/server/models/utils.ts | |||
@@ -1,25 +1,29 @@ | |||
1 | import { Sequelize } from 'sequelize-typescript' | 1 | import { Sequelize } from 'sequelize-typescript' |
2 | import * as validator from 'validator' | ||
3 | import { OrderItem } from 'sequelize' | ||
4 | import { Col } from 'sequelize/types/lib/utils' | ||
2 | 5 | ||
3 | type SortType = { sortModel: any, sortValue: string } | 6 | type SortType = { sortModel: any, sortValue: string } |
4 | 7 | ||
5 | // Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ] | 8 | // Translate for example "-name" to [ [ 'name', 'DESC' ], [ 'id', 'ASC' ] ] |
6 | function getSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | 9 | function getSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
7 | let { direction, field } = buildDirectionAndField(value) | 10 | const { direction, field } = buildDirectionAndField(value) |
11 | |||
12 | let finalField: string | Col | ||
8 | 13 | ||
9 | if (field.toLowerCase() === 'match') { // Search | 14 | if (field.toLowerCase() === 'match') { // Search |
10 | field = Sequelize.col('similarity') | 15 | finalField = Sequelize.col('similarity') |
16 | } else { | ||
17 | finalField = field | ||
11 | } | 18 | } |
12 | 19 | ||
13 | return [ [ field, direction ], lastSort ] | 20 | return [ [ finalField, direction ], lastSort ] |
14 | } | 21 | } |
15 | 22 | ||
16 | function getVideoSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | 23 | function getVideoSort (value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
17 | let { direction, field } = buildDirectionAndField(value) | 24 | const { direction, field } = buildDirectionAndField(value) |
18 | 25 | ||
19 | // Alias | 26 | if (field.toLowerCase() === 'trending') { // Sort by aggregation |
20 | if (field.toLowerCase() === 'match') { // Search | ||
21 | field = Sequelize.col('similarity') | ||
22 | } else if (field.toLowerCase() === 'trending') { // Sort by aggregation | ||
23 | return [ | 27 | return [ |
24 | [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ], | 28 | [ Sequelize.fn('COALESCE', Sequelize.fn('SUM', Sequelize.col('VideoViews.views')), '0'), direction ], |
25 | 29 | ||
@@ -29,21 +33,40 @@ function getVideoSort (value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | |||
29 | ] | 33 | ] |
30 | } | 34 | } |
31 | 35 | ||
32 | const firstSort = typeof field === 'string' ? | 36 | let finalField: string | Col |
33 | field.split('.').concat([ direction ]) : | 37 | |
34 | [ field, direction ] | 38 | // Alias |
39 | if (field.toLowerCase() === 'match') { // Search | ||
40 | finalField = Sequelize.col('similarity') | ||
41 | } else { | ||
42 | finalField = field | ||
43 | } | ||
44 | |||
45 | const firstSort = typeof finalField === 'string' | ||
46 | ? finalField.split('.').concat([ direction ]) as any // FIXME: sequelize typings | ||
47 | : [ finalField, direction ] | ||
35 | 48 | ||
36 | return [ firstSort, lastSort ] | 49 | return [ firstSort, lastSort ] |
37 | } | 50 | } |
38 | 51 | ||
39 | function getSortOnModel (model: any, value: string, lastSort: string[] = [ 'id', 'ASC' ]) { | 52 | function getSortOnModel (model: any, value: string, lastSort: OrderItem = [ 'id', 'ASC' ]): OrderItem[] { |
40 | let [ firstSort ] = getSort(value) | 53 | const [ firstSort ] = getSort(value) |
41 | 54 | ||
42 | if (model) return [ [ model, firstSort[0], firstSort[1] ], lastSort ] | 55 | if (model) return [ [ model, firstSort[0], firstSort[1] ], lastSort ] |
43 | return [ firstSort, lastSort ] | 56 | return [ firstSort, lastSort ] |
44 | } | 57 | } |
45 | 58 | ||
46 | function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value') { | 59 | function isOutdated (model: { createdAt: Date, updatedAt: Date }, refreshInterval: number) { |
60 | const now = Date.now() | ||
61 | const createdAtTime = model.createdAt.getTime() | ||
62 | const updatedAtTime = model.updatedAt.getTime() | ||
63 | |||
64 | return (now - createdAtTime) > refreshInterval && (now - updatedAtTime) > refreshInterval | ||
65 | } | ||
66 | |||
67 | function throwIfNotValid (value: any, validator: (value: any) => boolean, fieldName = 'value', nullable = false) { | ||
68 | if (nullable && (value === null || value === undefined)) return | ||
69 | |||
47 | if (validator(value) === false) { | 70 | if (validator(value) === false) { |
48 | throw new Error(`"${value}" is not a valid ${fieldName}.`) | 71 | throw new Error(`"${value}" is not a valid ${fieldName}.`) |
49 | } | 72 | } |
@@ -74,13 +97,34 @@ function buildBlockedAccountSQL (serverAccountId: number, userAccountId?: number | |||
74 | 97 | ||
75 | const blockerIdsString = blockerIds.join(', ') | 98 | const blockerIdsString = blockerIds.join(', ') |
76 | 99 | ||
77 | const query = 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + | 100 | return 'SELECT "targetAccountId" AS "id" FROM "accountBlocklist" WHERE "accountId" IN (' + blockerIdsString + ')' + |
78 | ' UNION ALL ' + | 101 | ' UNION ALL ' + |
79 | 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' + | 102 | 'SELECT "account"."id" AS "id" FROM account INNER JOIN "actor" ON account."actorId" = actor.id ' + |
80 | 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' + | 103 | 'INNER JOIN "serverBlocklist" ON "actor"."serverId" = "serverBlocklist"."targetServerId" ' + |
81 | 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' | 104 | 'WHERE "serverBlocklist"."accountId" IN (' + blockerIdsString + ')' |
105 | } | ||
106 | |||
107 | function buildServerIdsFollowedBy (actorId: any) { | ||
108 | const actorIdNumber = parseInt(actorId + '', 10) | ||
109 | |||
110 | return '(' + | ||
111 | 'SELECT "actor"."serverId" FROM "actorFollow" ' + | ||
112 | 'INNER JOIN "actor" ON actor.id = "actorFollow"."targetActorId" ' + | ||
113 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | ||
114 | ')' | ||
115 | } | ||
116 | |||
117 | function buildWhereIdOrUUID (id: number | string) { | ||
118 | return validator.isInt('' + id) ? { id } : { uuid: id } | ||
119 | } | ||
120 | |||
121 | function parseAggregateResult (result: any) { | ||
122 | if (!result) return 0 | ||
123 | |||
124 | const total = parseInt(result + '', 10) | ||
125 | if (isNaN(total)) return 0 | ||
82 | 126 | ||
83 | return query | 127 | return total |
84 | } | 128 | } |
85 | 129 | ||
86 | // --------------------------------------------------------------------------- | 130 | // --------------------------------------------------------------------------- |
@@ -93,7 +137,11 @@ export { | |||
93 | getSortOnModel, | 137 | getSortOnModel, |
94 | createSimilarityAttribute, | 138 | createSimilarityAttribute, |
95 | throwIfNotValid, | 139 | throwIfNotValid, |
96 | buildTrigramSearchIndex | 140 | buildServerIdsFollowedBy, |
141 | buildTrigramSearchIndex, | ||
142 | buildWhereIdOrUUID, | ||
143 | isOutdated, | ||
144 | parseAggregateResult | ||
97 | } | 145 | } |
98 | 146 | ||
99 | // --------------------------------------------------------------------------- | 147 | // --------------------------------------------------------------------------- |
@@ -107,7 +155,7 @@ function searchTrigramNormalizeCol (col: string) { | |||
107 | } | 155 | } |
108 | 156 | ||
109 | function buildDirectionAndField (value: string) { | 157 | function buildDirectionAndField (value: string) { |
110 | let field: any | 158 | let field: string |
111 | let direction: 'ASC' | 'DESC' | 159 | let direction: 'ASC' | 'DESC' |
112 | 160 | ||
113 | if (value.substring(0, 1) === '-') { | 161 | if (value.substring(0, 1) === '-') { |
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts index 1e56562e1..603d55692 100644 --- a/server/models/video/schedule-video-update.ts +++ b/server/models/video/schedule-video-update.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Sequelize, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' | 2 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' |
3 | import { VideoPrivacy } from '../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../shared/models/videos' |
4 | import { Transaction } from 'sequelize' | 4 | import { Op, Transaction } from 'sequelize' |
5 | 5 | ||
6 | @Table({ | 6 | @Table({ |
7 | tableName: 'scheduleVideoUpdate', | 7 | tableName: 'scheduleVideoUpdate', |
@@ -51,7 +51,7 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> { | |||
51 | attributes: [ 'id' ], | 51 | attributes: [ 'id' ], |
52 | where: { | 52 | where: { |
53 | updateAt: { | 53 | updateAt: { |
54 | [Sequelize.Op.lte]: new Date() | 54 | [Op.lte]: new Date() |
55 | } | 55 | } |
56 | } | 56 | } |
57 | } | 57 | } |
@@ -64,7 +64,7 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> { | |||
64 | const query = { | 64 | const query = { |
65 | where: { | 65 | where: { |
66 | updateAt: { | 66 | updateAt: { |
67 | [Sequelize.Op.lte]: new Date() | 67 | [Op.lte]: new Date() |
68 | } | 68 | } |
69 | }, | 69 | }, |
70 | include: [ | 70 | include: [ |
@@ -72,7 +72,9 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> { | |||
72 | model: VideoModel.scope( | 72 | model: VideoModel.scope( |
73 | [ | 73 | [ |
74 | VideoScopeNames.WITH_FILES, | 74 | VideoScopeNames.WITH_FILES, |
75 | VideoScopeNames.WITH_ACCOUNT_DETAILS | 75 | VideoScopeNames.WITH_ACCOUNT_DETAILS, |
76 | VideoScopeNames.WITH_BLACKLISTED, | ||
77 | VideoScopeNames.WITH_THUMBNAILS | ||
76 | ] | 78 | ] |
77 | ) | 79 | ) |
78 | } | 80 | } |
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index b39621eaf..0fc3cfd4c 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import * as Sequelize from 'sequelize' | 2 | import { QueryTypes, Transaction } from 'sequelize' |
3 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
4 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' | 4 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' |
5 | import { throwIfNotValid } from '../utils' | 5 | import { throwIfNotValid } from '../utils' |
@@ -37,7 +37,7 @@ export class TagModel extends Model<TagModel> { | |||
37 | }) | 37 | }) |
38 | Videos: VideoModel[] | 38 | Videos: VideoModel[] |
39 | 39 | ||
40 | static findOrCreateTags (tags: string[], transaction: Sequelize.Transaction) { | 40 | static findOrCreateTags (tags: string[], transaction: Transaction) { |
41 | if (tags === null) return [] | 41 | if (tags === null) return [] |
42 | 42 | ||
43 | const tasks: Bluebird<TagModel>[] = [] | 43 | const tasks: Bluebird<TagModel>[] = [] |
@@ -72,10 +72,10 @@ export class TagModel extends Model<TagModel> { | |||
72 | 72 | ||
73 | const options = { | 73 | const options = { |
74 | bind: { threshold, count, videoPrivacy: VideoPrivacy.PUBLIC, videoState: VideoState.PUBLISHED }, | 74 | bind: { threshold, count, videoPrivacy: VideoPrivacy.PUBLIC, videoState: VideoState.PUBLISHED }, |
75 | type: Sequelize.QueryTypes.SELECT | 75 | type: QueryTypes.SELECT as QueryTypes.SELECT |
76 | } | 76 | } |
77 | 77 | ||
78 | return TagModel.sequelize.query(query, options) | 78 | return TagModel.sequelize.query<{ name: string }>(query, options) |
79 | .then(data => data.map(d => d.name)) | 79 | .then(data => data.map(d => d.name)) |
80 | } | 80 | } |
81 | } | 81 | } |
diff --git a/server/models/video/thumbnail.ts b/server/models/video/thumbnail.ts new file mode 100644 index 000000000..206e9a3d6 --- /dev/null +++ b/server/models/video/thumbnail.ts | |||
@@ -0,0 +1,116 @@ | |||
1 | import { join } from 'path' | ||
2 | import { AfterDestroy, AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Table, UpdatedAt } from 'sequelize-typescript' | ||
3 | import { STATIC_PATHS, WEBSERVER } from '../../initializers/constants' | ||
4 | import { logger } from '../../helpers/logger' | ||
5 | import { remove } from 'fs-extra' | ||
6 | import { CONFIG } from '../../initializers/config' | ||
7 | import { VideoModel } from './video' | ||
8 | import { VideoPlaylistModel } from './video-playlist' | ||
9 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
10 | |||
11 | @Table({ | ||
12 | tableName: 'thumbnail', | ||
13 | indexes: [ | ||
14 | { | ||
15 | fields: [ 'videoId' ] | ||
16 | }, | ||
17 | { | ||
18 | fields: [ 'videoPlaylistId' ], | ||
19 | unique: true | ||
20 | } | ||
21 | ] | ||
22 | }) | ||
23 | export class ThumbnailModel extends Model<ThumbnailModel> { | ||
24 | |||
25 | @AllowNull(false) | ||
26 | @Column | ||
27 | filename: string | ||
28 | |||
29 | @AllowNull(true) | ||
30 | @Default(null) | ||
31 | @Column | ||
32 | height: number | ||
33 | |||
34 | @AllowNull(true) | ||
35 | @Default(null) | ||
36 | @Column | ||
37 | width: number | ||
38 | |||
39 | @AllowNull(false) | ||
40 | @Column | ||
41 | type: ThumbnailType | ||
42 | |||
43 | @AllowNull(true) | ||
44 | @Column | ||
45 | fileUrl: string | ||
46 | |||
47 | @ForeignKey(() => VideoModel) | ||
48 | @Column | ||
49 | videoId: number | ||
50 | |||
51 | @BelongsTo(() => VideoModel, { | ||
52 | foreignKey: { | ||
53 | allowNull: true | ||
54 | }, | ||
55 | onDelete: 'CASCADE' | ||
56 | }) | ||
57 | Video: VideoModel | ||
58 | |||
59 | @ForeignKey(() => VideoPlaylistModel) | ||
60 | @Column | ||
61 | videoPlaylistId: number | ||
62 | |||
63 | @BelongsTo(() => VideoPlaylistModel, { | ||
64 | foreignKey: { | ||
65 | allowNull: true | ||
66 | }, | ||
67 | onDelete: 'CASCADE' | ||
68 | }) | ||
69 | VideoPlaylist: VideoPlaylistModel | ||
70 | |||
71 | @CreatedAt | ||
72 | createdAt: Date | ||
73 | |||
74 | @UpdatedAt | ||
75 | updatedAt: Date | ||
76 | |||
77 | private static types: { [ id in ThumbnailType ]: { label: string, directory: string, staticPath: string } } = { | ||
78 | [ThumbnailType.MINIATURE]: { | ||
79 | label: 'miniature', | ||
80 | directory: CONFIG.STORAGE.THUMBNAILS_DIR, | ||
81 | staticPath: STATIC_PATHS.THUMBNAILS | ||
82 | }, | ||
83 | [ThumbnailType.PREVIEW]: { | ||
84 | label: 'preview', | ||
85 | directory: CONFIG.STORAGE.PREVIEWS_DIR, | ||
86 | staticPath: STATIC_PATHS.PREVIEWS | ||
87 | } | ||
88 | } | ||
89 | |||
90 | @AfterDestroy | ||
91 | static removeFilesAndSendDelete (instance: ThumbnailModel) { | ||
92 | logger.info('Removing %s file %s.', ThumbnailModel.types[instance.type].label, instance.filename) | ||
93 | |||
94 | // Don't block the transaction | ||
95 | instance.removeThumbnail() | ||
96 | .catch(err => logger.error('Cannot remove thumbnail file %s.', instance.filename, err)) | ||
97 | } | ||
98 | |||
99 | static generateDefaultPreviewName (videoUUID: string) { | ||
100 | return videoUUID + '.jpg' | ||
101 | } | ||
102 | |||
103 | getFileUrl () { | ||
104 | if (this.fileUrl) return this.fileUrl | ||
105 | |||
106 | const staticPath = ThumbnailModel.types[this.type].staticPath | ||
107 | return WEBSERVER.URL + staticPath + this.filename | ||
108 | } | ||
109 | |||
110 | removeThumbnail () { | ||
111 | const directory = ThumbnailModel.types[this.type].directory | ||
112 | const thumbnailPath = join(directory, this.filename) | ||
113 | |||
114 | return remove(thumbnailPath) | ||
115 | } | ||
116 | } | ||
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index cc47644f2..1ac7919b3 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -10,7 +10,7 @@ import { AccountModel } from '../account/account' | |||
10 | import { getSort, throwIfNotValid } from '../utils' | 10 | import { getSort, throwIfNotValid } from '../utils' |
11 | import { VideoModel } from './video' | 11 | import { VideoModel } from './video' |
12 | import { VideoAbuseState } from '../../../shared' | 12 | import { VideoAbuseState } from '../../../shared' |
13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers' | 13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' |
14 | 14 | ||
15 | @Table({ | 15 | @Table({ |
16 | tableName: 'videoAbuse', | 16 | tableName: 'videoAbuse', |
@@ -39,7 +39,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
39 | 39 | ||
40 | @AllowNull(true) | 40 | @AllowNull(true) |
41 | @Default(null) | 41 | @Default(null) |
42 | @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isVideoAbuseModerationCommentValid, 'moderationComment')) | 42 | @Is('VideoAbuseModerationComment', value => throwIfNotValid(value, isVideoAbuseModerationCommentValid, 'moderationComment', true)) |
43 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) | 43 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_ABUSES.MODERATION_COMMENT.max)) |
44 | moderationComment: string | 44 | moderationComment: string |
45 | 45 | ||
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index 3b567e488..d9fe9dfc9 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts | |||
@@ -1,9 +1,11 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { getSortOnModel, SortType, throwIfNotValid } from '../utils' | 2 | import { getSortOnModel, SortType, throwIfNotValid } from '../utils' |
3 | import { VideoModel } from './video' | 3 | import { VideoModel } from './video' |
4 | import { isVideoBlacklistReasonValid } from '../../helpers/custom-validators/video-blacklist' | 4 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' |
5 | import { VideoBlacklist } from '../../../shared/models/videos' | 5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' |
6 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 6 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' |
7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
8 | import { FindOptions } from 'sequelize' | ||
7 | 9 | ||
8 | @Table({ | 10 | @Table({ |
9 | tableName: 'videoBlacklist', | 11 | tableName: 'videoBlacklist', |
@@ -17,7 +19,7 @@ import { CONSTRAINTS_FIELDS } from '../../initializers' | |||
17 | export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | 19 | export class VideoBlacklistModel extends Model<VideoBlacklistModel> { |
18 | 20 | ||
19 | @AllowNull(true) | 21 | @AllowNull(true) |
20 | @Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason')) | 22 | @Is('VideoBlacklistReason', value => throwIfNotValid(value, isVideoBlacklistReasonValid, 'reason', true)) |
21 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_BLACKLIST.REASON.max)) | 23 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_BLACKLIST.REASON.max)) |
22 | reason: string | 24 | reason: string |
23 | 25 | ||
@@ -25,6 +27,12 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
25 | @Column | 27 | @Column |
26 | unfederated: boolean | 28 | unfederated: boolean |
27 | 29 | ||
30 | @AllowNull(false) | ||
31 | @Default(null) | ||
32 | @Is('VideoBlacklistType', value => throwIfNotValid(value, isVideoBlacklistTypeValid, 'type')) | ||
33 | @Column | ||
34 | type: VideoBlacklistType | ||
35 | |||
28 | @CreatedAt | 36 | @CreatedAt |
29 | createdAt: Date | 37 | createdAt: Date |
30 | 38 | ||
@@ -43,19 +51,29 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
43 | }) | 51 | }) |
44 | Video: VideoModel | 52 | Video: VideoModel |
45 | 53 | ||
46 | static listForApi (start: number, count: number, sort: SortType) { | 54 | static listForApi (start: number, count: number, sort: SortType, type?: VideoBlacklistType) { |
47 | const query = { | 55 | const query: FindOptions = { |
48 | offset: start, | 56 | offset: start, |
49 | limit: count, | 57 | limit: count, |
50 | order: getSortOnModel(sort.sortModel, sort.sortValue), | 58 | order: getSortOnModel(sort.sortModel, sort.sortValue), |
51 | include: [ | 59 | include: [ |
52 | { | 60 | { |
53 | model: VideoModel, | 61 | model: VideoModel, |
54 | required: true | 62 | required: true, |
63 | include: [ | ||
64 | { | ||
65 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }), | ||
66 | required: true | ||
67 | } | ||
68 | ] | ||
55 | } | 69 | } |
56 | ] | 70 | ] |
57 | } | 71 | } |
58 | 72 | ||
73 | if (type) { | ||
74 | query.where = { type } | ||
75 | } | ||
76 | |||
59 | return VideoBlacklistModel.findAndCountAll(query) | 77 | return VideoBlacklistModel.findAndCountAll(query) |
60 | .then(({ rows, count }) => { | 78 | .then(({ rows, count }) => { |
61 | return { | 79 | return { |
@@ -76,26 +94,15 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
76 | } | 94 | } |
77 | 95 | ||
78 | toFormattedJSON (): VideoBlacklist { | 96 | toFormattedJSON (): VideoBlacklist { |
79 | const video = this.Video | ||
80 | |||
81 | return { | 97 | return { |
82 | id: this.id, | 98 | id: this.id, |
83 | createdAt: this.createdAt, | 99 | createdAt: this.createdAt, |
84 | updatedAt: this.updatedAt, | 100 | updatedAt: this.updatedAt, |
85 | reason: this.reason, | 101 | reason: this.reason, |
86 | unfederated: this.unfederated, | 102 | unfederated: this.unfederated, |
103 | type: this.type, | ||
87 | 104 | ||
88 | video: { | 105 | video: this.Video.toFormattedJSON() |
89 | id: video.id, | ||
90 | name: video.name, | ||
91 | uuid: video.uuid, | ||
92 | description: video.description, | ||
93 | duration: video.duration, | ||
94 | views: video.views, | ||
95 | likes: video.likes, | ||
96 | dislikes: video.dislikes, | ||
97 | nsfw: video.nsfw | ||
98 | } | ||
99 | } | 106 | } |
100 | } | 107 | } |
101 | } | 108 | } |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index b4f17b481..76243bf48 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import { OrderItem, Transaction } from 'sequelize' |
2 | import { | 2 | import { |
3 | AllowNull, | 3 | AllowNull, |
4 | BeforeDestroy, | 4 | BeforeDestroy, |
@@ -12,30 +12,31 @@ import { | |||
12 | Table, | 12 | Table, |
13 | UpdatedAt | 13 | UpdatedAt |
14 | } from 'sequelize-typescript' | 14 | } from 'sequelize-typescript' |
15 | import { throwIfNotValid } from '../utils' | 15 | import { buildWhereIdOrUUID, throwIfNotValid } from '../utils' |
16 | import { VideoModel } from './video' | 16 | import { VideoModel } from './video' |
17 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' | 17 | import { isVideoCaptionLanguageValid } from '../../helpers/custom-validators/video-captions' |
18 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' | 18 | import { VideoCaption } from '../../../shared/models/videos/caption/video-caption.model' |
19 | import { CONFIG, STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers' | 19 | import { STATIC_PATHS, VIDEO_LANGUAGES } from '../../initializers/constants' |
20 | import { join } from 'path' | 20 | import { join } from 'path' |
21 | import { logger } from '../../helpers/logger' | 21 | import { logger } from '../../helpers/logger' |
22 | import { remove } from 'fs-extra' | 22 | import { remove } from 'fs-extra' |
23 | import { CONFIG } from '../../initializers/config' | ||
23 | 24 | ||
24 | export enum ScopeNames { | 25 | export enum ScopeNames { |
25 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 26 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
26 | } | 27 | } |
27 | 28 | ||
28 | @Scopes({ | 29 | @Scopes(() => ({ |
29 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { | 30 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { |
30 | include: [ | 31 | include: [ |
31 | { | 32 | { |
32 | attributes: [ 'uuid', 'remote' ], | 33 | attributes: [ 'uuid', 'remote' ], |
33 | model: () => VideoModel.unscoped(), | 34 | model: VideoModel.unscoped(), |
34 | required: true | 35 | required: true |
35 | } | 36 | } |
36 | ] | 37 | ] |
37 | } | 38 | } |
38 | }) | 39 | })) |
39 | 40 | ||
40 | @Table({ | 41 | @Table({ |
41 | tableName: 'videoCaption', | 42 | tableName: 'videoCaption', |
@@ -96,12 +97,9 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
96 | const videoInclude = { | 97 | const videoInclude = { |
97 | model: VideoModel.unscoped(), | 98 | model: VideoModel.unscoped(), |
98 | attributes: [ 'id', 'remote', 'uuid' ], | 99 | attributes: [ 'id', 'remote', 'uuid' ], |
99 | where: { } | 100 | where: buildWhereIdOrUUID(videoId) |
100 | } | 101 | } |
101 | 102 | ||
102 | if (typeof videoId === 'string') videoInclude.where['uuid'] = videoId | ||
103 | else videoInclude.where['id'] = videoId | ||
104 | |||
105 | const query = { | 103 | const query = { |
106 | where: { | 104 | where: { |
107 | language | 105 | language |
@@ -114,19 +112,19 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
114 | return VideoCaptionModel.findOne(query) | 112 | return VideoCaptionModel.findOne(query) |
115 | } | 113 | } |
116 | 114 | ||
117 | static insertOrReplaceLanguage (videoId: number, language: string, transaction: Sequelize.Transaction) { | 115 | static insertOrReplaceLanguage (videoId: number, language: string, transaction: Transaction) { |
118 | const values = { | 116 | const values = { |
119 | videoId, | 117 | videoId, |
120 | language | 118 | language |
121 | } | 119 | } |
122 | 120 | ||
123 | return VideoCaptionModel.upsert<VideoCaptionModel>(values, { transaction, returning: true }) | 121 | return (VideoCaptionModel.upsert<VideoCaptionModel>(values, { transaction, returning: true }) as any) // FIXME: typings |
124 | .then(([ caption ]) => caption) | 122 | .then(([ caption ]) => caption) |
125 | } | 123 | } |
126 | 124 | ||
127 | static listVideoCaptions (videoId: number) { | 125 | static listVideoCaptions (videoId: number) { |
128 | const query = { | 126 | const query = { |
129 | order: [ [ 'language', 'ASC' ] ], | 127 | order: [ [ 'language', 'ASC' ] ] as OrderItem[], |
130 | where: { | 128 | where: { |
131 | videoId | 129 | videoId |
132 | } | 130 | } |
@@ -139,7 +137,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
139 | return VIDEO_LANGUAGES[language] || 'Unknown' | 137 | return VIDEO_LANGUAGES[language] || 'Unknown' |
140 | } | 138 | } |
141 | 139 | ||
142 | static deleteAllCaptionsOfRemoteVideo (videoId: number, transaction: Sequelize.Transaction) { | 140 | static deleteAllCaptionsOfRemoteVideo (videoId: number, transaction: Transaction) { |
143 | const query = { | 141 | const query = { |
144 | where: { | 142 | where: { |
145 | videoId | 143 | videoId |
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts index 48c07728f..b545a2f8c 100644 --- a/server/models/video/video-change-ownership.ts +++ b/server/models/video/video-change-ownership.ts | |||
@@ -1,51 +1,52 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { AccountModel } from '../account/account' | 2 | import { AccountModel } from '../account/account' |
3 | import { VideoModel } from './video' | 3 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' |
4 | import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' | 4 | import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' |
5 | import { getSort } from '../utils' | 5 | import { getSort } from '../utils' |
6 | import { VideoFileModel } from './video-file' | ||
7 | 6 | ||
8 | enum ScopeNames { | 7 | enum ScopeNames { |
9 | FULL = 'FULL' | 8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS', |
9 | WITH_VIDEO = 'WITH_VIDEO' | ||
10 | } | 10 | } |
11 | 11 | ||
12 | @Table({ | 12 | @Table({ |
13 | tableName: 'videoChangeOwnership', | 13 | tableName: 'videoChangeOwnership', |
14 | indexes: [ | 14 | indexes: [ |
15 | { | 15 | { |
16 | fields: ['videoId'] | 16 | fields: [ 'videoId' ] |
17 | }, | 17 | }, |
18 | { | 18 | { |
19 | fields: ['initiatorAccountId'] | 19 | fields: [ 'initiatorAccountId' ] |
20 | }, | 20 | }, |
21 | { | 21 | { |
22 | fields: ['nextOwnerAccountId'] | 22 | fields: [ 'nextOwnerAccountId' ] |
23 | } | 23 | } |
24 | ] | 24 | ] |
25 | }) | 25 | }) |
26 | @Scopes({ | 26 | @Scopes(() => ({ |
27 | [ScopeNames.FULL]: { | 27 | [ScopeNames.WITH_ACCOUNTS]: { |
28 | include: [ | 28 | include: [ |
29 | { | 29 | { |
30 | model: () => AccountModel, | 30 | model: AccountModel, |
31 | as: 'Initiator', | 31 | as: 'Initiator', |
32 | required: true | 32 | required: true |
33 | }, | 33 | }, |
34 | { | 34 | { |
35 | model: () => AccountModel, | 35 | model: AccountModel, |
36 | as: 'NextOwner', | 36 | as: 'NextOwner', |
37 | required: true | 37 | required: true |
38 | }, | 38 | } |
39 | ] | ||
40 | }, | ||
41 | [ScopeNames.WITH_VIDEO]: { | ||
42 | include: [ | ||
39 | { | 43 | { |
40 | model: () => VideoModel, | 44 | model: VideoModel.scope([ VideoScopeNames.WITH_THUMBNAILS, VideoScopeNames.WITH_FILES ]), |
41 | required: true, | 45 | required: true |
42 | include: [ | ||
43 | { model: () => VideoFileModel } | ||
44 | ] | ||
45 | } | 46 | } |
46 | ] | 47 | ] |
47 | } | 48 | } |
48 | }) | 49 | })) |
49 | export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> { | 50 | export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> { |
50 | @CreatedAt | 51 | @CreatedAt |
51 | createdAt: Date | 52 | createdAt: Date |
@@ -105,12 +106,15 @@ export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> | |||
105 | } | 106 | } |
106 | } | 107 | } |
107 | 108 | ||
108 | return VideoChangeOwnershipModel.scope(ScopeNames.FULL).findAndCountAll(query) | 109 | return Promise.all([ |
109 | .then(({ rows, count }) => ({ total: count, data: rows })) | 110 | VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query), |
111 | VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll(query) | ||
112 | ]).then(([ count, rows ]) => ({ total: count, data: rows })) | ||
110 | } | 113 | } |
111 | 114 | ||
112 | static load (id: number) { | 115 | static load (id: number) { |
113 | return VideoChangeOwnershipModel.scope(ScopeNames.FULL).findById(id) | 116 | return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]) |
117 | .findByPk(id) | ||
114 | } | 118 | } |
115 | 119 | ||
116 | toFormattedJSON (): VideoChangeOwnership { | 120 | toFormattedJSON (): VideoChangeOwnership { |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 5598d80f6..fb70e6625 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -17,23 +17,25 @@ import { | |||
17 | UpdatedAt | 17 | UpdatedAt |
18 | } from 'sequelize-typescript' | 18 | } from 'sequelize-typescript' |
19 | import { ActivityPubActor } from '../../../shared/models/activitypub' | 19 | import { ActivityPubActor } from '../../../shared/models/activitypub' |
20 | import { VideoChannel } from '../../../shared/models/videos' | 20 | import { VideoChannel, VideoChannelSummary } from '../../../shared/models/videos' |
21 | import { | 21 | import { |
22 | isVideoChannelDescriptionValid, | 22 | isVideoChannelDescriptionValid, |
23 | isVideoChannelNameValid, | 23 | isVideoChannelNameValid, |
24 | isVideoChannelSupportValid | 24 | isVideoChannelSupportValid |
25 | } from '../../helpers/custom-validators/video-channels' | 25 | } from '../../helpers/custom-validators/video-channels' |
26 | import { sendDeleteActor } from '../../lib/activitypub/send' | 26 | import { sendDeleteActor } from '../../lib/activitypub/send' |
27 | import { AccountModel } from '../account/account' | 27 | import { AccountModel, ScopeNames as AccountModelScopeNames } from '../account/account' |
28 | import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' | 28 | import { ActorModel, unusedActorAttributesForAPI } from '../activitypub/actor' |
29 | import { buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' | 29 | import { buildServerIdsFollowedBy, buildTrigramSearchIndex, createSimilarityAttribute, getSort, throwIfNotValid } from '../utils' |
30 | import { VideoModel } from './video' | 30 | import { VideoModel } from './video' |
31 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 31 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
32 | import { ServerModel } from '../server/server' | 32 | import { ServerModel } from '../server/server' |
33 | import { DefineIndexesOptions } from 'sequelize' | 33 | import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' |
34 | import { AvatarModel } from '../avatar/avatar' | ||
35 | import { VideoPlaylistModel } from './video-playlist' | ||
34 | 36 | ||
35 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 37 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
36 | const indexes: DefineIndexesOptions[] = [ | 38 | const indexes: ModelIndexesOptions[] = [ |
37 | buildTrigramSearchIndex('video_channel_name_trigram', 'name'), | 39 | buildTrigramSearchIndex('video_channel_name_trigram', 'name'), |
38 | 40 | ||
39 | { | 41 | { |
@@ -44,35 +46,62 @@ const indexes: DefineIndexesOptions[] = [ | |||
44 | } | 46 | } |
45 | ] | 47 | ] |
46 | 48 | ||
47 | enum ScopeNames { | 49 | export enum ScopeNames { |
48 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 50 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
49 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 51 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
50 | WITH_ACTOR = 'WITH_ACTOR', | 52 | WITH_ACTOR = 'WITH_ACTOR', |
51 | WITH_VIDEOS = 'WITH_VIDEOS' | 53 | WITH_VIDEOS = 'WITH_VIDEOS', |
54 | SUMMARY = 'SUMMARY' | ||
52 | } | 55 | } |
53 | 56 | ||
54 | type AvailableForListOptions = { | 57 | type AvailableForListOptions = { |
55 | actorId: number | 58 | actorId: number |
56 | } | 59 | } |
57 | 60 | ||
58 | @DefaultScope({ | 61 | @DefaultScope(() => ({ |
59 | include: [ | 62 | include: [ |
60 | { | 63 | { |
61 | model: () => ActorModel, | 64 | model: ActorModel, |
62 | required: true | 65 | required: true |
63 | } | 66 | } |
64 | ] | 67 | ] |
65 | }) | 68 | })) |
66 | @Scopes({ | 69 | @Scopes(() => ({ |
67 | [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { | 70 | [ScopeNames.SUMMARY]: (withAccount = false) => { |
68 | const actorIdNumber = parseInt(options.actorId + '', 10) | 71 | const base: FindOptions = { |
72 | attributes: [ 'name', 'description', 'id', 'actorId' ], | ||
73 | include: [ | ||
74 | { | ||
75 | attributes: [ 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], | ||
76 | model: ActorModel.unscoped(), | ||
77 | required: true, | ||
78 | include: [ | ||
79 | { | ||
80 | attributes: [ 'host' ], | ||
81 | model: ServerModel.unscoped(), | ||
82 | required: false | ||
83 | }, | ||
84 | { | ||
85 | model: AvatarModel.unscoped(), | ||
86 | required: false | ||
87 | } | ||
88 | ] | ||
89 | } | ||
90 | ] | ||
91 | } | ||
92 | |||
93 | if (withAccount === true) { | ||
94 | base.include.push({ | ||
95 | model: AccountModel.scope(AccountModelScopeNames.SUMMARY), | ||
96 | required: true | ||
97 | }) | ||
98 | } | ||
69 | 99 | ||
100 | return base | ||
101 | }, | ||
102 | [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { | ||
70 | // Only list local channels OR channels that are on an instance followed by actorId | 103 | // Only list local channels OR channels that are on an instance followed by actorId |
71 | const inQueryInstanceFollow = '(' + | 104 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) |
72 | 'SELECT "actor"."serverId" FROM "actorFollow" ' + | ||
73 | 'INNER JOIN "actor" ON actor.id= "actorFollow"."targetActorId" ' + | ||
74 | 'WHERE "actorFollow"."actorId" = ' + actorIdNumber + | ||
75 | ')' | ||
76 | 105 | ||
77 | return { | 106 | return { |
78 | include: [ | 107 | include: [ |
@@ -82,13 +111,13 @@ type AvailableForListOptions = { | |||
82 | }, | 111 | }, |
83 | model: ActorModel, | 112 | model: ActorModel, |
84 | where: { | 113 | where: { |
85 | [Sequelize.Op.or]: [ | 114 | [Op.or]: [ |
86 | { | 115 | { |
87 | serverId: null | 116 | serverId: null |
88 | }, | 117 | }, |
89 | { | 118 | { |
90 | serverId: { | 119 | serverId: { |
91 | [ Sequelize.Op.in ]: Sequelize.literal(inQueryInstanceFollow) | 120 | [ Op.in ]: Sequelize.literal(inQueryInstanceFollow) |
92 | } | 121 | } |
93 | } | 122 | } |
94 | ] | 123 | ] |
@@ -113,22 +142,22 @@ type AvailableForListOptions = { | |||
113 | [ScopeNames.WITH_ACCOUNT]: { | 142 | [ScopeNames.WITH_ACCOUNT]: { |
114 | include: [ | 143 | include: [ |
115 | { | 144 | { |
116 | model: () => AccountModel, | 145 | model: AccountModel, |
117 | required: true | 146 | required: true |
118 | } | 147 | } |
119 | ] | 148 | ] |
120 | }, | 149 | }, |
121 | [ScopeNames.WITH_VIDEOS]: { | 150 | [ScopeNames.WITH_VIDEOS]: { |
122 | include: [ | 151 | include: [ |
123 | () => VideoModel | 152 | VideoModel |
124 | ] | 153 | ] |
125 | }, | 154 | }, |
126 | [ScopeNames.WITH_ACTOR]: { | 155 | [ScopeNames.WITH_ACTOR]: { |
127 | include: [ | 156 | include: [ |
128 | () => ActorModel | 157 | ActorModel |
129 | ] | 158 | ] |
130 | } | 159 | } |
131 | }) | 160 | })) |
132 | @Table({ | 161 | @Table({ |
133 | tableName: 'videoChannel', | 162 | tableName: 'videoChannel', |
134 | indexes | 163 | indexes |
@@ -142,13 +171,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
142 | 171 | ||
143 | @AllowNull(true) | 172 | @AllowNull(true) |
144 | @Default(null) | 173 | @Default(null) |
145 | @Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description')) | 174 | @Is('VideoChannelDescription', value => throwIfNotValid(value, isVideoChannelDescriptionValid, 'description', true)) |
146 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.DESCRIPTION.max)) | 175 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.DESCRIPTION.max)) |
147 | description: string | 176 | description: string |
148 | 177 | ||
149 | @AllowNull(true) | 178 | @AllowNull(true) |
150 | @Default(null) | 179 | @Default(null) |
151 | @Is('VideoChannelSupport', value => throwIfNotValid(value, isVideoChannelSupportValid, 'support')) | 180 | @Is('VideoChannelSupport', value => throwIfNotValid(value, isVideoChannelSupportValid, 'support', true)) |
152 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.SUPPORT.max)) | 181 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.SUPPORT.max)) |
153 | support: string | 182 | support: string |
154 | 183 | ||
@@ -192,6 +221,15 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
192 | }) | 221 | }) |
193 | Videos: VideoModel[] | 222 | Videos: VideoModel[] |
194 | 223 | ||
224 | @HasMany(() => VideoPlaylistModel, { | ||
225 | foreignKey: { | ||
226 | allowNull: true | ||
227 | }, | ||
228 | onDelete: 'CASCADE', | ||
229 | hooks: true | ||
230 | }) | ||
231 | VideoPlaylists: VideoPlaylistModel[] | ||
232 | |||
195 | @BeforeDestroy | 233 | @BeforeDestroy |
196 | static async sendDeleteIfOwned (instance: VideoChannelModel, options) { | 234 | static async sendDeleteIfOwned (instance: VideoChannelModel, options) { |
197 | if (!instance.Actor) { | 235 | if (!instance.Actor) { |
@@ -274,7 +312,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
274 | limit: options.count, | 312 | limit: options.count, |
275 | order: getSort(options.sort), | 313 | order: getSort(options.sort), |
276 | where: { | 314 | where: { |
277 | [Sequelize.Op.or]: [ | 315 | [Op.or]: [ |
278 | Sequelize.literal( | 316 | Sequelize.literal( |
279 | 'lower(immutable_unaccent("VideoChannelModel"."name")) % lower(immutable_unaccent(' + escapedSearch + '))' | 317 | 'lower(immutable_unaccent("VideoChannelModel"."name")) % lower(immutable_unaccent(' + escapedSearch + '))' |
280 | ), | 318 | ), |
@@ -320,7 +358,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
320 | static loadByIdAndPopulateAccount (id: number) { | 358 | static loadByIdAndPopulateAccount (id: number) { |
321 | return VideoChannelModel.unscoped() | 359 | return VideoChannelModel.unscoped() |
322 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | 360 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) |
323 | .findById(id) | 361 | .findByPk(id) |
324 | } | 362 | } |
325 | 363 | ||
326 | static loadByIdAndAccount (id: number, accountId: number) { | 364 | static loadByIdAndAccount (id: number, accountId: number) { |
@@ -339,7 +377,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
339 | static loadAndPopulateAccount (id: number) { | 377 | static loadAndPopulateAccount (id: number) { |
340 | return VideoChannelModel.unscoped() | 378 | return VideoChannelModel.unscoped() |
341 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | 379 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) |
342 | .findById(id) | 380 | .findByPk(id) |
343 | } | 381 | } |
344 | 382 | ||
345 | static loadByUUIDAndPopulateAccount (uuid: string) { | 383 | static loadByUUIDAndPopulateAccount (uuid: string) { |
@@ -378,6 +416,14 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
378 | .findOne(query) | 416 | .findOne(query) |
379 | } | 417 | } |
380 | 418 | ||
419 | static loadByNameWithHostAndPopulateAccount (nameWithHost: string) { | ||
420 | const [ name, host ] = nameWithHost.split('@') | ||
421 | |||
422 | if (!host || host === WEBSERVER.HOST) return VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | ||
423 | |||
424 | return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) | ||
425 | } | ||
426 | |||
381 | static loadLocalByNameAndPopulateAccount (name: string) { | 427 | static loadLocalByNameAndPopulateAccount (name: string) { |
382 | const query = { | 428 | const query = { |
383 | include: [ | 429 | include: [ |
@@ -431,7 +477,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
431 | 477 | ||
432 | return VideoChannelModel.unscoped() | 478 | return VideoChannelModel.unscoped() |
433 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]) | 479 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEOS ]) |
434 | .findById(id, options) | 480 | .findByPk(id, options) |
435 | } | 481 | } |
436 | 482 | ||
437 | toFormattedJSON (): VideoChannel { | 483 | toFormattedJSON (): VideoChannel { |
@@ -452,6 +498,20 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
452 | return Object.assign(actor, videoChannel) | 498 | return Object.assign(actor, videoChannel) |
453 | } | 499 | } |
454 | 500 | ||
501 | toFormattedSummaryJSON (): VideoChannelSummary { | ||
502 | const actor = this.Actor.toFormattedJSON() | ||
503 | |||
504 | return { | ||
505 | id: this.id, | ||
506 | uuid: actor.uuid, | ||
507 | name: actor.name, | ||
508 | displayName: this.getDisplayName(), | ||
509 | url: actor.url, | ||
510 | host: actor.host, | ||
511 | avatar: actor.avatar | ||
512 | } | ||
513 | } | ||
514 | |||
455 | toActivityPubObject (): ActivityPubActor { | 515 | toActivityPubObject (): ActivityPubActor { |
456 | const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel') | 516 | const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel') |
457 | 517 | ||
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 1163f9a0e..fee11ec5f 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | import { | 1 | import { |
3 | AllowNull, | 2 | AllowNull, |
4 | BeforeDestroy, | 3 | BeforeDestroy, |
@@ -7,7 +6,6 @@ import { | |||
7 | CreatedAt, | 6 | CreatedAt, |
8 | DataType, | 7 | DataType, |
9 | ForeignKey, | 8 | ForeignKey, |
10 | IFindOptions, | ||
11 | Is, | 9 | Is, |
12 | Model, | 10 | Model, |
13 | Scopes, | 11 | Scopes, |
@@ -18,7 +16,7 @@ import { ActivityTagObject } from '../../../shared/models/activitypub/objects/co | |||
18 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' | 16 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' |
19 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' | 17 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' |
20 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 18 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
21 | import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' | 19 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
22 | import { sendDeleteVideoComment } from '../../lib/activitypub/send' | 20 | import { sendDeleteVideoComment } from '../../lib/activitypub/send' |
23 | import { AccountModel } from '../account/account' | 21 | import { AccountModel } from '../account/account' |
24 | import { ActorModel } from '../activitypub/actor' | 22 | import { ActorModel } from '../activitypub/actor' |
@@ -32,6 +30,7 @@ import { UserModel } from '../account/user' | |||
32 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' | 30 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' |
33 | import { regexpCapture } from '../../helpers/regexp' | 31 | import { regexpCapture } from '../../helpers/regexp' |
34 | import { uniq } from 'lodash' | 32 | import { uniq } from 'lodash' |
33 | import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' | ||
35 | 34 | ||
36 | enum ScopeNames { | 35 | enum ScopeNames { |
37 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 36 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -40,7 +39,7 @@ enum ScopeNames { | |||
40 | ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API' | 39 | ATTRIBUTES_FOR_API = 'ATTRIBUTES_FOR_API' |
41 | } | 40 | } |
42 | 41 | ||
43 | @Scopes({ | 42 | @Scopes(() => ({ |
44 | [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { | 43 | [ScopeNames.ATTRIBUTES_FOR_API]: (serverAccountId: number, userAccountId?: number) => { |
45 | return { | 44 | return { |
46 | attributes: { | 45 | attributes: { |
@@ -64,22 +63,22 @@ enum ScopeNames { | |||
64 | ] | 63 | ] |
65 | ] | 64 | ] |
66 | } | 65 | } |
67 | } | 66 | } as FindOptions |
68 | }, | 67 | }, |
69 | [ScopeNames.WITH_ACCOUNT]: { | 68 | [ScopeNames.WITH_ACCOUNT]: { |
70 | include: [ | 69 | include: [ |
71 | { | 70 | { |
72 | model: () => AccountModel, | 71 | model: AccountModel, |
73 | include: [ | 72 | include: [ |
74 | { | 73 | { |
75 | model: () => ActorModel, | 74 | model: ActorModel, |
76 | include: [ | 75 | include: [ |
77 | { | 76 | { |
78 | model: () => ServerModel, | 77 | model: ServerModel, |
79 | required: false | 78 | required: false |
80 | }, | 79 | }, |
81 | { | 80 | { |
82 | model: () => AvatarModel, | 81 | model: AvatarModel, |
83 | required: false | 82 | required: false |
84 | } | 83 | } |
85 | ] | 84 | ] |
@@ -91,7 +90,7 @@ enum ScopeNames { | |||
91 | [ScopeNames.WITH_IN_REPLY_TO]: { | 90 | [ScopeNames.WITH_IN_REPLY_TO]: { |
92 | include: [ | 91 | include: [ |
93 | { | 92 | { |
94 | model: () => VideoCommentModel, | 93 | model: VideoCommentModel, |
95 | as: 'InReplyToVideoComment' | 94 | as: 'InReplyToVideoComment' |
96 | } | 95 | } |
97 | ] | 96 | ] |
@@ -99,19 +98,19 @@ enum ScopeNames { | |||
99 | [ScopeNames.WITH_VIDEO]: { | 98 | [ScopeNames.WITH_VIDEO]: { |
100 | include: [ | 99 | include: [ |
101 | { | 100 | { |
102 | model: () => VideoModel, | 101 | model: VideoModel, |
103 | required: true, | 102 | required: true, |
104 | include: [ | 103 | include: [ |
105 | { | 104 | { |
106 | model: () => VideoChannelModel.unscoped(), | 105 | model: VideoChannelModel.unscoped(), |
107 | required: true, | 106 | required: true, |
108 | include: [ | 107 | include: [ |
109 | { | 108 | { |
110 | model: () => AccountModel, | 109 | model: AccountModel, |
111 | required: true, | 110 | required: true, |
112 | include: [ | 111 | include: [ |
113 | { | 112 | { |
114 | model: () => ActorModel, | 113 | model: ActorModel, |
115 | required: true | 114 | required: true |
116 | } | 115 | } |
117 | ] | 116 | ] |
@@ -122,7 +121,7 @@ enum ScopeNames { | |||
122 | } | 121 | } |
123 | ] | 122 | ] |
124 | } | 123 | } |
125 | }) | 124 | })) |
126 | @Table({ | 125 | @Table({ |
127 | tableName: 'videoComment', | 126 | tableName: 'videoComment', |
128 | indexes: [ | 127 | indexes: [ |
@@ -244,8 +243,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
244 | } | 243 | } |
245 | } | 244 | } |
246 | 245 | ||
247 | static loadById (id: number, t?: Sequelize.Transaction) { | 246 | static loadById (id: number, t?: Transaction) { |
248 | const query: IFindOptions<VideoCommentModel> = { | 247 | const query: FindOptions = { |
249 | where: { | 248 | where: { |
250 | id | 249 | id |
251 | } | 250 | } |
@@ -256,8 +255,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
256 | return VideoCommentModel.findOne(query) | 255 | return VideoCommentModel.findOne(query) |
257 | } | 256 | } |
258 | 257 | ||
259 | static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Sequelize.Transaction) { | 258 | static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction) { |
260 | const query: IFindOptions<VideoCommentModel> = { | 259 | const query: FindOptions = { |
261 | where: { | 260 | where: { |
262 | id | 261 | id |
263 | } | 262 | } |
@@ -270,8 +269,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
270 | .findOne(query) | 269 | .findOne(query) |
271 | } | 270 | } |
272 | 271 | ||
273 | static loadByUrlAndPopulateAccount (url: string, t?: Sequelize.Transaction) { | 272 | static loadByUrlAndPopulateAccount (url: string, t?: Transaction) { |
274 | const query: IFindOptions<VideoCommentModel> = { | 273 | const query: FindOptions = { |
275 | where: { | 274 | where: { |
276 | url | 275 | url |
277 | } | 276 | } |
@@ -282,8 +281,8 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
282 | return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT ]).findOne(query) | 281 | return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT ]).findOne(query) |
283 | } | 282 | } |
284 | 283 | ||
285 | static loadByUrlAndPopulateReplyAndVideo (url: string, t?: Sequelize.Transaction) { | 284 | static loadByUrlAndPopulateReplyAndVideo (url: string, t?: Transaction) { |
286 | const query: IFindOptions<VideoCommentModel> = { | 285 | const query: FindOptions = { |
287 | where: { | 286 | where: { |
288 | url | 287 | url |
289 | } | 288 | } |
@@ -307,15 +306,14 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
307 | videoId, | 306 | videoId, |
308 | inReplyToCommentId: null, | 307 | inReplyToCommentId: null, |
309 | accountId: { | 308 | accountId: { |
310 | [Sequelize.Op.notIn]: Sequelize.literal( | 309 | [Op.notIn]: Sequelize.literal( |
311 | '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' | 310 | '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' |
312 | ) | 311 | ) |
313 | } | 312 | } |
314 | } | 313 | } |
315 | } | 314 | } |
316 | 315 | ||
317 | // FIXME: typings | 316 | const scopes: (string | ScopeOptions)[] = [ |
318 | const scopes: any[] = [ | ||
319 | ScopeNames.WITH_ACCOUNT, | 317 | ScopeNames.WITH_ACCOUNT, |
320 | { | 318 | { |
321 | method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] | 319 | method: [ ScopeNames.ATTRIBUTES_FOR_API, serverAccountId, userAccountId ] |
@@ -336,15 +334,15 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
336 | const userAccountId = user ? user.Account.id : undefined | 334 | const userAccountId = user ? user.Account.id : undefined |
337 | 335 | ||
338 | const query = { | 336 | const query = { |
339 | order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ], | 337 | order: [ [ 'createdAt', 'ASC' ], [ 'updatedAt', 'ASC' ] ] as Order, |
340 | where: { | 338 | where: { |
341 | videoId, | 339 | videoId, |
342 | [ Sequelize.Op.or ]: [ | 340 | [ Op.or ]: [ |
343 | { id: threadId }, | 341 | { id: threadId }, |
344 | { originCommentId: threadId } | 342 | { originCommentId: threadId } |
345 | ], | 343 | ], |
346 | accountId: { | 344 | accountId: { |
347 | [Sequelize.Op.notIn]: Sequelize.literal( | 345 | [Op.notIn]: Sequelize.literal( |
348 | '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' | 346 | '(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')' |
349 | ) | 347 | ) |
350 | } | 348 | } |
@@ -366,12 +364,12 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
366 | }) | 364 | }) |
367 | } | 365 | } |
368 | 366 | ||
369 | static listThreadParentComments (comment: VideoCommentModel, t: Sequelize.Transaction, order: 'ASC' | 'DESC' = 'ASC') { | 367 | static listThreadParentComments (comment: VideoCommentModel, t: Transaction, order: 'ASC' | 'DESC' = 'ASC') { |
370 | const query = { | 368 | const query = { |
371 | order: [ [ 'createdAt', order ] ], | 369 | order: [ [ 'createdAt', order ] ] as Order, |
372 | where: { | 370 | where: { |
373 | id: { | 371 | id: { |
374 | [ Sequelize.Op.in ]: Sequelize.literal('(' + | 372 | [ Op.in ]: Sequelize.literal('(' + |
375 | 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + | 373 | 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + |
376 | `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + | 374 | `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + |
377 | 'UNION ' + | 375 | 'UNION ' + |
@@ -380,7 +378,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
380 | ') ' + | 378 | ') ' + |
381 | 'SELECT id FROM children' + | 379 | 'SELECT id FROM children' + |
382 | ')'), | 380 | ')'), |
383 | [ Sequelize.Op.ne ]: comment.id | 381 | [ Op.ne ]: comment.id |
384 | } | 382 | } |
385 | }, | 383 | }, |
386 | transaction: t | 384 | transaction: t |
@@ -391,9 +389,9 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
391 | .findAll(query) | 389 | .findAll(query) |
392 | } | 390 | } |
393 | 391 | ||
394 | static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Sequelize.Transaction, order: 'ASC' | 'DESC' = 'ASC') { | 392 | static listAndCountByVideoId (videoId: number, start: number, count: number, t?: Transaction, order: 'ASC' | 'DESC' = 'ASC') { |
395 | const query = { | 393 | const query = { |
396 | order: [ [ 'createdAt', order ] ], | 394 | order: [ [ 'createdAt', order ] ] as Order, |
397 | offset: start, | 395 | offset: start, |
398 | limit: count, | 396 | limit: count, |
399 | where: { | 397 | where: { |
@@ -407,7 +405,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
407 | 405 | ||
408 | static listForFeed (start: number, count: number, videoId?: number) { | 406 | static listForFeed (start: number, count: number, videoId?: number) { |
409 | const query = { | 407 | const query = { |
410 | order: [ [ 'createdAt', 'DESC' ] ], | 408 | order: [ [ 'createdAt', 'DESC' ] ] as Order, |
411 | offset: start, | 409 | offset: start, |
412 | limit: count, | 410 | limit: count, |
413 | where: {}, | 411 | where: {}, |
@@ -453,6 +451,19 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
453 | } | 451 | } |
454 | } | 452 | } |
455 | 453 | ||
454 | static cleanOldCommentsOf (videoId: number, beforeUpdatedAt: Date) { | ||
455 | const query = { | ||
456 | where: { | ||
457 | updatedAt: { | ||
458 | [Op.lt]: beforeUpdatedAt | ||
459 | }, | ||
460 | videoId | ||
461 | } | ||
462 | } | ||
463 | |||
464 | return VideoCommentModel.destroy(query) | ||
465 | } | ||
466 | |||
456 | getCommentStaticPath () { | 467 | getCommentStaticPath () { |
457 | return this.Video.getWatchStaticPath() + ';threadId=' + this.getThreadId() | 468 | return this.Video.getWatchStaticPath() + ';threadId=' + this.getThreadId() |
458 | } | 469 | } |
@@ -469,7 +480,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
469 | let result: string[] = [] | 480 | let result: string[] = [] |
470 | 481 | ||
471 | const localMention = `@(${actorNameAlphabet}+)` | 482 | const localMention = `@(${actorNameAlphabet}+)` |
472 | const remoteMention = `${localMention}@${CONFIG.WEBSERVER.HOST}` | 483 | const remoteMention = `${localMention}@${WEBSERVER.HOST}` |
473 | 484 | ||
474 | const mentionRegex = this.isOwned() | 485 | const mentionRegex = this.isOwned() |
475 | ? '(?:(?:' + remoteMention + ')|(?:' + localMention + '))' // Include local mentions? | 486 | ? '(?:(?:' + remoteMention + ')|(?:' + localMention + '))' // Include local mentions? |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index 1f1b76c1e..2203a7aba 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -19,10 +19,11 @@ import { | |||
19 | isVideoFileSizeValid, | 19 | isVideoFileSizeValid, |
20 | isVideoFPSResolutionValid | 20 | isVideoFPSResolutionValid |
21 | } from '../../helpers/custom-validators/videos' | 21 | } from '../../helpers/custom-validators/videos' |
22 | import { throwIfNotValid } from '../utils' | 22 | import { parseAggregateResult, throwIfNotValid } from '../utils' |
23 | import { VideoModel } from './video' | 23 | import { VideoModel } from './video' |
24 | import * as Sequelize from 'sequelize' | ||
25 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 24 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
25 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | ||
26 | import { FindOptions, QueryTypes, Transaction } from 'sequelize' | ||
26 | 27 | ||
27 | @Table({ | 28 | @Table({ |
28 | tableName: 'videoFile', | 29 | tableName: 'videoFile', |
@@ -62,7 +63,7 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
62 | extname: string | 63 | extname: string |
63 | 64 | ||
64 | @AllowNull(false) | 65 | @AllowNull(false) |
65 | @Is('VideoFileSize', value => throwIfNotValid(value, isVideoFileInfoHashValid, 'info hash')) | 66 | @Is('VideoFileInfohash', value => throwIfNotValid(value, isVideoFileInfoHashValid, 'info hash')) |
66 | @Column | 67 | @Column |
67 | infoHash: string | 68 | infoHash: string |
68 | 69 | ||
@@ -86,25 +87,23 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
86 | 87 | ||
87 | @HasMany(() => VideoRedundancyModel, { | 88 | @HasMany(() => VideoRedundancyModel, { |
88 | foreignKey: { | 89 | foreignKey: { |
89 | allowNull: false | 90 | allowNull: true |
90 | }, | 91 | }, |
91 | onDelete: 'CASCADE', | 92 | onDelete: 'CASCADE', |
92 | hooks: true | 93 | hooks: true |
93 | }) | 94 | }) |
94 | RedundancyVideos: VideoRedundancyModel[] | 95 | RedundancyVideos: VideoRedundancyModel[] |
95 | 96 | ||
96 | static isInfohashExists (infoHash: string) { | 97 | static doesInfohashExist (infoHash: string) { |
97 | const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' | 98 | const query = 'SELECT 1 FROM "videoFile" WHERE "infoHash" = $infoHash LIMIT 1' |
98 | const options = { | 99 | const options = { |
99 | type: Sequelize.QueryTypes.SELECT, | 100 | type: QueryTypes.SELECT, |
100 | bind: { infoHash }, | 101 | bind: { infoHash }, |
101 | raw: true | 102 | raw: true |
102 | } | 103 | } |
103 | 104 | ||
104 | return VideoModel.sequelize.query(query, options) | 105 | return VideoModel.sequelize.query(query, options) |
105 | .then(results => { | 106 | .then(results => results.length === 1) |
106 | return results.length === 1 | ||
107 | }) | ||
108 | } | 107 | } |
109 | 108 | ||
110 | static loadWithVideo (id: number) { | 109 | static loadWithVideo (id: number) { |
@@ -117,11 +116,34 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
117 | ] | 116 | ] |
118 | } | 117 | } |
119 | 118 | ||
120 | return VideoFileModel.findById(id, options) | 119 | return VideoFileModel.findByPk(id, options) |
121 | } | 120 | } |
122 | 121 | ||
123 | static async getStats () { | 122 | static listByStreamingPlaylist (streamingPlaylistId: number, transaction: Transaction) { |
124 | let totalLocalVideoFilesSize = await VideoFileModel.sum('size', { | 123 | const query = { |
124 | include: [ | ||
125 | { | ||
126 | model: VideoModel.unscoped(), | ||
127 | required: true, | ||
128 | include: [ | ||
129 | { | ||
130 | model: VideoStreamingPlaylistModel.unscoped(), | ||
131 | required: true, | ||
132 | where: { | ||
133 | id: streamingPlaylistId | ||
134 | } | ||
135 | } | ||
136 | ] | ||
137 | } | ||
138 | ], | ||
139 | transaction | ||
140 | } | ||
141 | |||
142 | return VideoFileModel.findAll(query) | ||
143 | } | ||
144 | |||
145 | static getStats () { | ||
146 | const query: FindOptions = { | ||
125 | include: [ | 147 | include: [ |
126 | { | 148 | { |
127 | attributes: [], | 149 | attributes: [], |
@@ -131,13 +153,12 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
131 | } | 153 | } |
132 | } | 154 | } |
133 | ] | 155 | ] |
134 | } as any) | ||
135 | // Sequelize could return null... | ||
136 | if (!totalLocalVideoFilesSize) totalLocalVideoFilesSize = 0 | ||
137 | |||
138 | return { | ||
139 | totalLocalVideoFilesSize | ||
140 | } | 156 | } |
157 | |||
158 | return VideoFileModel.aggregate('size', 'SUM', query) | ||
159 | .then(result => ({ | ||
160 | totalLocalVideoFilesSize: parseAggregateResult(result) | ||
161 | })) | ||
141 | } | 162 | } |
142 | 163 | ||
143 | hasSameUniqueKeysThan (other: VideoFileModel) { | 164 | hasSameUniqueKeysThan (other: VideoFileModel) { |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index de0747f22..b947eb16f 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -1,8 +1,13 @@ | |||
1 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 1 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
2 | import { VideoModel } from './video' | 2 | import { VideoModel } from './video' |
3 | import { VideoFileModel } from './video-file' | 3 | import { VideoFileModel } from './video-file' |
4 | import { ActivityUrlObject, VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 4 | import { |
5 | import { CONFIG, MIMETYPES, THUMBNAILS_SIZE } from '../../initializers' | 5 | ActivityPlaylistInfohashesObject, |
6 | ActivityPlaylistSegmentHashesObject, | ||
7 | ActivityUrlObject, | ||
8 | VideoTorrentObject | ||
9 | } from '../../../shared/models/activitypub/objects' | ||
10 | import { MIMETYPES, WEBSERVER } from '../../initializers/constants' | ||
6 | import { VideoCaptionModel } from './video-caption' | 11 | import { VideoCaptionModel } from './video-caption' |
7 | import { | 12 | import { |
8 | getVideoCommentsActivityPubUrl, | 13 | getVideoCommentsActivityPubUrl, |
@@ -11,6 +16,8 @@ import { | |||
11 | getVideoSharesActivityPubUrl | 16 | getVideoSharesActivityPubUrl |
12 | } from '../../lib/activitypub' | 17 | } from '../../lib/activitypub' |
13 | import { isArray } from '../../helpers/custom-validators/misc' | 18 | import { isArray } from '../../helpers/custom-validators/misc' |
19 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' | ||
20 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | ||
14 | 21 | ||
15 | export type VideoFormattingJSONOptions = { | 22 | export type VideoFormattingJSONOptions = { |
16 | completeDescription?: boolean | 23 | completeDescription?: boolean |
@@ -19,12 +26,10 @@ export type VideoFormattingJSONOptions = { | |||
19 | waitTranscoding?: boolean, | 26 | waitTranscoding?: boolean, |
20 | scheduledUpdate?: boolean, | 27 | scheduledUpdate?: boolean, |
21 | blacklistInfo?: boolean | 28 | blacklistInfo?: boolean |
29 | playlistInfo?: boolean | ||
22 | } | 30 | } |
23 | } | 31 | } |
24 | function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { | 32 | function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { |
25 | const formattedAccount = video.VideoChannel.Account.toFormattedJSON() | ||
26 | const formattedVideoChannel = video.VideoChannel.toFormattedJSON() | ||
27 | |||
28 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined | 33 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined |
29 | 34 | ||
30 | const videoObject: Video = { | 35 | const videoObject: Video = { |
@@ -54,30 +59,16 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
54 | views: video.views, | 59 | views: video.views, |
55 | likes: video.likes, | 60 | likes: video.likes, |
56 | dislikes: video.dislikes, | 61 | dislikes: video.dislikes, |
57 | thumbnailPath: video.getThumbnailStaticPath(), | 62 | thumbnailPath: video.getMiniatureStaticPath(), |
58 | previewPath: video.getPreviewStaticPath(), | 63 | previewPath: video.getPreviewStaticPath(), |
59 | embedPath: video.getEmbedStaticPath(), | 64 | embedPath: video.getEmbedStaticPath(), |
60 | createdAt: video.createdAt, | 65 | createdAt: video.createdAt, |
61 | updatedAt: video.updatedAt, | 66 | updatedAt: video.updatedAt, |
62 | publishedAt: video.publishedAt, | 67 | publishedAt: video.publishedAt, |
63 | account: { | 68 | originallyPublishedAt: video.originallyPublishedAt, |
64 | id: formattedAccount.id, | 69 | |
65 | uuid: formattedAccount.uuid, | 70 | account: video.VideoChannel.Account.toFormattedSummaryJSON(), |
66 | name: formattedAccount.name, | 71 | channel: video.VideoChannel.toFormattedSummaryJSON(), |
67 | displayName: formattedAccount.displayName, | ||
68 | url: formattedAccount.url, | ||
69 | host: formattedAccount.host, | ||
70 | avatar: formattedAccount.avatar | ||
71 | }, | ||
72 | channel: { | ||
73 | id: formattedVideoChannel.id, | ||
74 | uuid: formattedVideoChannel.uuid, | ||
75 | name: formattedVideoChannel.name, | ||
76 | displayName: formattedVideoChannel.displayName, | ||
77 | url: formattedVideoChannel.url, | ||
78 | host: formattedVideoChannel.host, | ||
79 | avatar: formattedVideoChannel.avatar | ||
80 | }, | ||
81 | 72 | ||
82 | userHistory: userHistory ? { | 73 | userHistory: userHistory ? { |
83 | currentTime: userHistory.currentTime | 74 | currentTime: userHistory.currentTime |
@@ -107,6 +98,17 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
107 | videoObject.blacklisted = !!video.VideoBlacklist | 98 | videoObject.blacklisted = !!video.VideoBlacklist |
108 | videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null | 99 | videoObject.blacklistedReason = video.VideoBlacklist ? video.VideoBlacklist.reason : null |
109 | } | 100 | } |
101 | |||
102 | if (options.additionalAttributes.playlistInfo === true) { | ||
103 | // We filtered on a specific videoId/videoPlaylistId, that is unique | ||
104 | const playlistElement = video.VideoPlaylistElements[0] | ||
105 | |||
106 | videoObject.playlistElement = { | ||
107 | position: playlistElement.position, | ||
108 | startTimestamp: playlistElement.startTimestamp, | ||
109 | stopTimestamp: playlistElement.stopTimestamp | ||
110 | } | ||
111 | } | ||
110 | } | 112 | } |
111 | 113 | ||
112 | return videoObject | 114 | return videoObject |
@@ -120,7 +122,12 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | |||
120 | } | 122 | } |
121 | }) | 123 | }) |
122 | 124 | ||
125 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | ||
126 | |||
123 | const tags = video.Tags ? video.Tags.map(t => t.name) : [] | 127 | const tags = video.Tags ? video.Tags.map(t => t.name) : [] |
128 | |||
129 | const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) | ||
130 | |||
124 | const detailsJson = { | 131 | const detailsJson = { |
125 | support: video.support, | 132 | support: video.support, |
126 | descriptionPath: video.getDescriptionAPIPath(), | 133 | descriptionPath: video.getDescriptionAPIPath(), |
@@ -128,12 +135,17 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | |||
128 | account: video.VideoChannel.Account.toFormattedJSON(), | 135 | account: video.VideoChannel.Account.toFormattedJSON(), |
129 | tags, | 136 | tags, |
130 | commentsEnabled: video.commentsEnabled, | 137 | commentsEnabled: video.commentsEnabled, |
138 | downloadEnabled: video.downloadEnabled, | ||
131 | waitTranscoding: video.waitTranscoding, | 139 | waitTranscoding: video.waitTranscoding, |
132 | state: { | 140 | state: { |
133 | id: video.state, | 141 | id: video.state, |
134 | label: VideoModel.getStateLabel(video.state) | 142 | label: VideoModel.getStateLabel(video.state) |
135 | }, | 143 | }, |
136 | files: [] | 144 | |
145 | trackerUrls: video.getTrackerUrls(baseUrlHttp, baseUrlWs), | ||
146 | |||
147 | files: [], | ||
148 | streamingPlaylists | ||
137 | } | 149 | } |
138 | 150 | ||
139 | // Format and sort video files | 151 | // Format and sort video files |
@@ -142,6 +154,25 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | |||
142 | return Object.assign(formattedJson, detailsJson) | 154 | return Object.assign(formattedJson, detailsJson) |
143 | } | 155 | } |
144 | 156 | ||
157 | function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: VideoStreamingPlaylistModel[]): VideoStreamingPlaylist[] { | ||
158 | if (isArray(playlists) === false) return [] | ||
159 | |||
160 | return playlists | ||
161 | .map(playlist => { | ||
162 | const redundancies = isArray(playlist.RedundancyVideos) | ||
163 | ? playlist.RedundancyVideos.map(r => ({ baseUrl: r.fileUrl })) | ||
164 | : [] | ||
165 | |||
166 | return { | ||
167 | id: playlist.id, | ||
168 | type: playlist.type, | ||
169 | playlistUrl: playlist.playlistUrl, | ||
170 | segmentsSha256Url: playlist.segmentsSha256Url, | ||
171 | redundancies | ||
172 | } as VideoStreamingPlaylist | ||
173 | }) | ||
174 | } | ||
175 | |||
145 | function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] { | 176 | function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] { |
146 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 177 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
147 | 178 | ||
@@ -232,12 +263,34 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
232 | }) | 263 | }) |
233 | } | 264 | } |
234 | 265 | ||
266 | for (const playlist of (video.VideoStreamingPlaylists || [])) { | ||
267 | let tag: (ActivityPlaylistSegmentHashesObject | ActivityPlaylistInfohashesObject)[] | ||
268 | |||
269 | tag = playlist.p2pMediaLoaderInfohashes | ||
270 | .map(i => ({ type: 'Infohash' as 'Infohash', name: i })) | ||
271 | tag.push({ | ||
272 | type: 'Link', | ||
273 | name: 'sha256', | ||
274 | mimeType: 'application/json' as 'application/json', | ||
275 | mediaType: 'application/json' as 'application/json', | ||
276 | href: playlist.segmentsSha256Url | ||
277 | }) | ||
278 | |||
279 | url.push({ | ||
280 | type: 'Link', | ||
281 | mimeType: 'application/x-mpegURL' as 'application/x-mpegURL', | ||
282 | mediaType: 'application/x-mpegURL' as 'application/x-mpegURL', | ||
283 | href: playlist.playlistUrl, | ||
284 | tag | ||
285 | }) | ||
286 | } | ||
287 | |||
235 | // Add video url too | 288 | // Add video url too |
236 | url.push({ | 289 | url.push({ |
237 | type: 'Link', | 290 | type: 'Link', |
238 | mimeType: 'text/html', | 291 | mimeType: 'text/html', |
239 | mediaType: 'text/html', | 292 | mediaType: 'text/html', |
240 | href: CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid | 293 | href: WEBSERVER.URL + '/videos/watch/' + video.uuid |
241 | }) | 294 | }) |
242 | 295 | ||
243 | const subtitleLanguage = [] | 296 | const subtitleLanguage = [] |
@@ -248,6 +301,8 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
248 | }) | 301 | }) |
249 | } | 302 | } |
250 | 303 | ||
304 | const miniature = video.getMiniature() | ||
305 | |||
251 | return { | 306 | return { |
252 | type: 'Video' as 'Video', | 307 | type: 'Video' as 'Video', |
253 | id: video.url, | 308 | id: video.url, |
@@ -263,7 +318,9 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
263 | waitTranscoding: video.waitTranscoding, | 318 | waitTranscoding: video.waitTranscoding, |
264 | state: video.state, | 319 | state: video.state, |
265 | commentsEnabled: video.commentsEnabled, | 320 | commentsEnabled: video.commentsEnabled, |
321 | downloadEnabled: video.downloadEnabled, | ||
266 | published: video.publishedAt.toISOString(), | 322 | published: video.publishedAt.toISOString(), |
323 | originallyPublishedAt: video.originallyPublishedAt ? video.originallyPublishedAt.toISOString() : null, | ||
267 | updated: video.updatedAt.toISOString(), | 324 | updated: video.updatedAt.toISOString(), |
268 | mediaType: 'text/markdown', | 325 | mediaType: 'text/markdown', |
269 | content: video.getTruncatedDescription(), | 326 | content: video.getTruncatedDescription(), |
@@ -271,10 +328,10 @@ function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | |||
271 | subtitleLanguage, | 328 | subtitleLanguage, |
272 | icon: { | 329 | icon: { |
273 | type: 'Image', | 330 | type: 'Image', |
274 | url: video.getThumbnailUrl(baseUrlHttp), | 331 | url: miniature.getFileUrl(), |
275 | mediaType: 'image/jpeg', | 332 | mediaType: 'image/jpeg', |
276 | width: THUMBNAILS_SIZE.width, | 333 | width: miniature.width, |
277 | height: THUMBNAILS_SIZE.height | 334 | height: miniature.height |
278 | }, | 335 | }, |
279 | url, | 336 | url, |
280 | likes: getVideoLikesActivityPubUrl(video), | 337 | likes: getVideoLikesActivityPubUrl(video), |
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index c723e57c0..480a671c8 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -13,7 +13,7 @@ import { | |||
13 | Table, | 13 | Table, |
14 | UpdatedAt | 14 | UpdatedAt |
15 | } from 'sequelize-typescript' | 15 | } from 'sequelize-typescript' |
16 | import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers' | 16 | import { CONSTRAINTS_FIELDS, VIDEO_IMPORT_STATES } from '../../initializers/constants' |
17 | import { getSort, throwIfNotValid } from '../utils' | 17 | import { getSort, throwIfNotValid } from '../utils' |
18 | import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' | 18 | import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' |
19 | import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' | 19 | import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../helpers/custom-validators/video-imports' |
@@ -21,18 +21,18 @@ import { VideoImport, VideoImportState } from '../../../shared' | |||
21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' | 21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' |
22 | import { UserModel } from '../account/user' | 22 | import { UserModel } from '../account/user' |
23 | 23 | ||
24 | @DefaultScope({ | 24 | @DefaultScope(() => ({ |
25 | include: [ | 25 | include: [ |
26 | { | 26 | { |
27 | model: () => UserModel.unscoped(), | 27 | model: UserModel.unscoped(), |
28 | required: true | 28 | required: true |
29 | }, | 29 | }, |
30 | { | 30 | { |
31 | model: () => VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), | 31 | model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), |
32 | required: false | 32 | required: false |
33 | } | 33 | } |
34 | ] | 34 | ] |
35 | }) | 35 | })) |
36 | 36 | ||
37 | @Table({ | 37 | @Table({ |
38 | tableName: 'videoImport', | 38 | tableName: 'videoImport', |
@@ -55,13 +55,13 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
55 | 55 | ||
56 | @AllowNull(true) | 56 | @AllowNull(true) |
57 | @Default(null) | 57 | @Default(null) |
58 | @Is('VideoImportTargetUrl', value => throwIfNotValid(value, isVideoImportTargetUrlValid, 'targetUrl')) | 58 | @Is('VideoImportTargetUrl', value => throwIfNotValid(value, isVideoImportTargetUrlValid, 'targetUrl', true)) |
59 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) | 59 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) |
60 | targetUrl: string | 60 | targetUrl: string |
61 | 61 | ||
62 | @AllowNull(true) | 62 | @AllowNull(true) |
63 | @Default(null) | 63 | @Default(null) |
64 | @Is('VideoImportMagnetUri', value => throwIfNotValid(value, isVideoMagnetUriValid, 'magnetUri')) | 64 | @Is('VideoImportMagnetUri', value => throwIfNotValid(value, isVideoMagnetUriValid, 'magnetUri', true)) |
65 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) // Use the same constraints than URLs | 65 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_IMPORTS.URL.max)) // Use the same constraints than URLs |
66 | magnetUri: string | 66 | magnetUri: string |
67 | 67 | ||
@@ -115,7 +115,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
115 | } | 115 | } |
116 | 116 | ||
117 | static loadAndPopulateVideo (id: number) { | 117 | static loadAndPopulateVideo (id: number) { |
118 | return VideoImportModel.findById(id) | 118 | return VideoImportModel.findByPk(id) |
119 | } | 119 | } |
120 | 120 | ||
121 | static listUserVideoImportsForApi (userId: number, start: number, count: number, sort: string) { | 121 | static listUserVideoImportsForApi (userId: number, start: number, count: number, sort: string) { |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts new file mode 100644 index 000000000..eeb3d6bbd --- /dev/null +++ b/server/models/video/video-playlist-element.ts | |||
@@ -0,0 +1,230 @@ | |||
1 | import { | ||
2 | AllowNull, | ||
3 | BelongsTo, | ||
4 | Column, | ||
5 | CreatedAt, | ||
6 | DataType, | ||
7 | Default, | ||
8 | ForeignKey, | ||
9 | Is, | ||
10 | IsInt, | ||
11 | Min, | ||
12 | Model, | ||
13 | Table, | ||
14 | UpdatedAt | ||
15 | } from 'sequelize-typescript' | ||
16 | import { VideoModel } from './video' | ||
17 | import { VideoPlaylistModel } from './video-playlist' | ||
18 | import { getSort, throwIfNotValid } from '../utils' | ||
19 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | ||
20 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | ||
21 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' | ||
22 | import * as validator from 'validator' | ||
23 | import { AggregateOptions, Op, Sequelize, Transaction } from 'sequelize' | ||
24 | |||
25 | @Table({ | ||
26 | tableName: 'videoPlaylistElement', | ||
27 | indexes: [ | ||
28 | { | ||
29 | fields: [ 'videoPlaylistId' ] | ||
30 | }, | ||
31 | { | ||
32 | fields: [ 'videoId' ] | ||
33 | }, | ||
34 | { | ||
35 | fields: [ 'videoPlaylistId', 'videoId' ], | ||
36 | unique: true | ||
37 | }, | ||
38 | { | ||
39 | fields: [ 'url' ], | ||
40 | unique: true | ||
41 | } | ||
42 | ] | ||
43 | }) | ||
44 | export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> { | ||
45 | @CreatedAt | ||
46 | createdAt: Date | ||
47 | |||
48 | @UpdatedAt | ||
49 | updatedAt: Date | ||
50 | |||
51 | @AllowNull(false) | ||
52 | @Is('VideoPlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | ||
53 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.URL.max)) | ||
54 | url: string | ||
55 | |||
56 | @AllowNull(false) | ||
57 | @Default(1) | ||
58 | @IsInt | ||
59 | @Min(1) | ||
60 | @Column | ||
61 | position: number | ||
62 | |||
63 | @AllowNull(true) | ||
64 | @IsInt | ||
65 | @Min(0) | ||
66 | @Column | ||
67 | startTimestamp: number | ||
68 | |||
69 | @AllowNull(true) | ||
70 | @IsInt | ||
71 | @Min(0) | ||
72 | @Column | ||
73 | stopTimestamp: number | ||
74 | |||
75 | @ForeignKey(() => VideoPlaylistModel) | ||
76 | @Column | ||
77 | videoPlaylistId: number | ||
78 | |||
79 | @BelongsTo(() => VideoPlaylistModel, { | ||
80 | foreignKey: { | ||
81 | allowNull: false | ||
82 | }, | ||
83 | onDelete: 'CASCADE' | ||
84 | }) | ||
85 | VideoPlaylist: VideoPlaylistModel | ||
86 | |||
87 | @ForeignKey(() => VideoModel) | ||
88 | @Column | ||
89 | videoId: number | ||
90 | |||
91 | @BelongsTo(() => VideoModel, { | ||
92 | foreignKey: { | ||
93 | allowNull: false | ||
94 | }, | ||
95 | onDelete: 'CASCADE' | ||
96 | }) | ||
97 | Video: VideoModel | ||
98 | |||
99 | static deleteAllOf (videoPlaylistId: number, transaction?: Transaction) { | ||
100 | const query = { | ||
101 | where: { | ||
102 | videoPlaylistId | ||
103 | }, | ||
104 | transaction | ||
105 | } | ||
106 | |||
107 | return VideoPlaylistElementModel.destroy(query) | ||
108 | } | ||
109 | |||
110 | static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { | ||
111 | const query = { | ||
112 | where: { | ||
113 | videoPlaylistId, | ||
114 | videoId | ||
115 | } | ||
116 | } | ||
117 | |||
118 | return VideoPlaylistElementModel.findOne(query) | ||
119 | } | ||
120 | |||
121 | static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { | ||
122 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } | ||
123 | const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } | ||
124 | |||
125 | const query = { | ||
126 | include: [ | ||
127 | { | ||
128 | attributes: [ 'privacy' ], | ||
129 | model: VideoPlaylistModel.unscoped(), | ||
130 | where: playlistWhere | ||
131 | }, | ||
132 | { | ||
133 | attributes: [ 'url' ], | ||
134 | model: VideoModel.unscoped(), | ||
135 | where: videoWhere | ||
136 | } | ||
137 | ] | ||
138 | } | ||
139 | |||
140 | return VideoPlaylistElementModel.findOne(query) | ||
141 | } | ||
142 | |||
143 | static listUrlsOfForAP (videoPlaylistId: number, start: number, count: number, t?: Transaction) { | ||
144 | const query = { | ||
145 | attributes: [ 'url' ], | ||
146 | offset: start, | ||
147 | limit: count, | ||
148 | order: getSort('position'), | ||
149 | where: { | ||
150 | videoPlaylistId | ||
151 | }, | ||
152 | transaction: t | ||
153 | } | ||
154 | |||
155 | return VideoPlaylistElementModel | ||
156 | .findAndCountAll(query) | ||
157 | .then(({ rows, count }) => { | ||
158 | return { total: count, data: rows.map(e => e.url) } | ||
159 | }) | ||
160 | } | ||
161 | |||
162 | static getNextPositionOf (videoPlaylistId: number, transaction?: Transaction) { | ||
163 | const query: AggregateOptions<number> = { | ||
164 | where: { | ||
165 | videoPlaylistId | ||
166 | }, | ||
167 | transaction | ||
168 | } | ||
169 | |||
170 | return VideoPlaylistElementModel.max('position', query) | ||
171 | .then(position => position ? position + 1 : 1) | ||
172 | } | ||
173 | |||
174 | static reassignPositionOf ( | ||
175 | videoPlaylistId: number, | ||
176 | firstPosition: number, | ||
177 | endPosition: number, | ||
178 | newPosition: number, | ||
179 | transaction?: Transaction | ||
180 | ) { | ||
181 | const query = { | ||
182 | where: { | ||
183 | videoPlaylistId, | ||
184 | position: { | ||
185 | [Op.gte]: firstPosition, | ||
186 | [Op.lte]: endPosition | ||
187 | } | ||
188 | }, | ||
189 | transaction, | ||
190 | validate: false // We use a literal to update the position | ||
191 | } | ||
192 | |||
193 | return VideoPlaylistElementModel.update({ position: Sequelize.literal(`${newPosition} + "position" - ${firstPosition}`) }, query) | ||
194 | } | ||
195 | |||
196 | static increasePositionOf ( | ||
197 | videoPlaylistId: number, | ||
198 | fromPosition: number, | ||
199 | toPosition?: number, | ||
200 | by = 1, | ||
201 | transaction?: Transaction | ||
202 | ) { | ||
203 | const query = { | ||
204 | where: { | ||
205 | videoPlaylistId, | ||
206 | position: { | ||
207 | [Op.gte]: fromPosition | ||
208 | } | ||
209 | }, | ||
210 | transaction | ||
211 | } | ||
212 | |||
213 | return VideoPlaylistElementModel.increment({ position: by }, query) | ||
214 | } | ||
215 | |||
216 | toActivityPubObject (): PlaylistElementObject { | ||
217 | const base: PlaylistElementObject = { | ||
218 | id: this.url, | ||
219 | type: 'PlaylistElement', | ||
220 | |||
221 | url: this.Video.url, | ||
222 | position: this.position | ||
223 | } | ||
224 | |||
225 | if (this.startTimestamp) base.startTimestamp = this.startTimestamp | ||
226 | if (this.stopTimestamp) base.stopTimestamp = this.stopTimestamp | ||
227 | |||
228 | return base | ||
229 | } | ||
230 | } | ||
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts new file mode 100644 index 000000000..63b4a0715 --- /dev/null +++ b/server/models/video/video-playlist.ts | |||
@@ -0,0 +1,531 @@ | |||
1 | import { | ||
2 | AllowNull, | ||
3 | BelongsTo, | ||
4 | Column, | ||
5 | CreatedAt, | ||
6 | DataType, | ||
7 | Default, | ||
8 | ForeignKey, | ||
9 | HasMany, | ||
10 | HasOne, | ||
11 | Is, | ||
12 | IsUUID, | ||
13 | Model, | ||
14 | Scopes, | ||
15 | Table, | ||
16 | UpdatedAt | ||
17 | } from 'sequelize-typescript' | ||
18 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
19 | import { buildServerIdsFollowedBy, buildWhereIdOrUUID, getSort, isOutdated, throwIfNotValid } from '../utils' | ||
20 | import { | ||
21 | isVideoPlaylistDescriptionValid, | ||
22 | isVideoPlaylistNameValid, | ||
23 | isVideoPlaylistPrivacyValid | ||
24 | } from '../../helpers/custom-validators/video-playlists' | ||
25 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | ||
26 | import { | ||
27 | ACTIVITY_PUB, | ||
28 | CONSTRAINTS_FIELDS, | ||
29 | STATIC_PATHS, | ||
30 | THUMBNAILS_SIZE, | ||
31 | VIDEO_PLAYLIST_PRIVACIES, | ||
32 | VIDEO_PLAYLIST_TYPES, | ||
33 | WEBSERVER | ||
34 | } from '../../initializers/constants' | ||
35 | import { VideoPlaylist } from '../../../shared/models/videos/playlist/video-playlist.model' | ||
36 | import { AccountModel, ScopeNames as AccountScopeNames } from '../account/account' | ||
37 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' | ||
38 | import { join } from 'path' | ||
39 | import { VideoPlaylistElementModel } from './video-playlist-element' | ||
40 | import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' | ||
41 | import { activityPubCollectionPagination } from '../../helpers/activitypub' | ||
42 | import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video-playlist-type.model' | ||
43 | import { ThumbnailModel } from './thumbnail' | ||
44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' | ||
45 | import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' | ||
46 | |||
47 | enum ScopeNames { | ||
48 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | ||
49 | WITH_VIDEOS_LENGTH = 'WITH_VIDEOS_LENGTH', | ||
50 | WITH_ACCOUNT_AND_CHANNEL_SUMMARY = 'WITH_ACCOUNT_AND_CHANNEL_SUMMARY', | ||
51 | WITH_ACCOUNT = 'WITH_ACCOUNT', | ||
52 | WITH_THUMBNAIL = 'WITH_THUMBNAIL', | ||
53 | WITH_ACCOUNT_AND_CHANNEL = 'WITH_ACCOUNT_AND_CHANNEL' | ||
54 | } | ||
55 | |||
56 | type AvailableForListOptions = { | ||
57 | followerActorId: number | ||
58 | type?: VideoPlaylistType | ||
59 | accountId?: number | ||
60 | videoChannelId?: number | ||
61 | privateAndUnlisted?: boolean | ||
62 | } | ||
63 | |||
64 | @Scopes(() => ({ | ||
65 | [ ScopeNames.WITH_THUMBNAIL ]: { | ||
66 | include: [ | ||
67 | { | ||
68 | model: ThumbnailModel, | ||
69 | required: false | ||
70 | } | ||
71 | ] | ||
72 | }, | ||
73 | [ ScopeNames.WITH_VIDEOS_LENGTH ]: { | ||
74 | attributes: { | ||
75 | include: [ | ||
76 | [ | ||
77 | literal('(SELECT COUNT("id") FROM "videoPlaylistElement" WHERE "videoPlaylistId" = "VideoPlaylistModel"."id")'), | ||
78 | 'videosLength' | ||
79 | ] | ||
80 | ] | ||
81 | } | ||
82 | } as FindOptions, | ||
83 | [ ScopeNames.WITH_ACCOUNT ]: { | ||
84 | include: [ | ||
85 | { | ||
86 | model: AccountModel, | ||
87 | required: true | ||
88 | } | ||
89 | ] | ||
90 | }, | ||
91 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY ]: { | ||
92 | include: [ | ||
93 | { | ||
94 | model: AccountModel.scope(AccountScopeNames.SUMMARY), | ||
95 | required: true | ||
96 | }, | ||
97 | { | ||
98 | model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), | ||
99 | required: false | ||
100 | } | ||
101 | ] | ||
102 | }, | ||
103 | [ ScopeNames.WITH_ACCOUNT_AND_CHANNEL ]: { | ||
104 | include: [ | ||
105 | { | ||
106 | model: AccountModel, | ||
107 | required: true | ||
108 | }, | ||
109 | { | ||
110 | model: VideoChannelModel, | ||
111 | required: false | ||
112 | } | ||
113 | ] | ||
114 | }, | ||
115 | [ ScopeNames.AVAILABLE_FOR_LIST ]: (options: AvailableForListOptions) => { | ||
116 | // Only list local playlists OR playlists that are on an instance followed by actorId | ||
117 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.followerActorId) | ||
118 | const actorWhere = { | ||
119 | [ Op.or ]: [ | ||
120 | { | ||
121 | serverId: null | ||
122 | }, | ||
123 | { | ||
124 | serverId: { | ||
125 | [ Op.in ]: literal(inQueryInstanceFollow) | ||
126 | } | ||
127 | } | ||
128 | ] | ||
129 | } | ||
130 | |||
131 | const whereAnd: WhereOptions[] = [] | ||
132 | |||
133 | if (options.privateAndUnlisted !== true) { | ||
134 | whereAnd.push({ | ||
135 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
136 | }) | ||
137 | } | ||
138 | |||
139 | if (options.accountId) { | ||
140 | whereAnd.push({ | ||
141 | ownerAccountId: options.accountId | ||
142 | }) | ||
143 | } | ||
144 | |||
145 | if (options.videoChannelId) { | ||
146 | whereAnd.push({ | ||
147 | videoChannelId: options.videoChannelId | ||
148 | }) | ||
149 | } | ||
150 | |||
151 | if (options.type) { | ||
152 | whereAnd.push({ | ||
153 | type: options.type | ||
154 | }) | ||
155 | } | ||
156 | |||
157 | const where = { | ||
158 | [Op.and]: whereAnd | ||
159 | } | ||
160 | |||
161 | const accountScope = { | ||
162 | method: [ AccountScopeNames.SUMMARY, actorWhere ] | ||
163 | } | ||
164 | |||
165 | return { | ||
166 | where, | ||
167 | include: [ | ||
168 | { | ||
169 | model: AccountModel.scope(accountScope), | ||
170 | required: true | ||
171 | }, | ||
172 | { | ||
173 | model: VideoChannelModel.scope(VideoChannelScopeNames.SUMMARY), | ||
174 | required: false | ||
175 | } | ||
176 | ] | ||
177 | } as FindOptions | ||
178 | } | ||
179 | })) | ||
180 | |||
181 | @Table({ | ||
182 | tableName: 'videoPlaylist', | ||
183 | indexes: [ | ||
184 | { | ||
185 | fields: [ 'ownerAccountId' ] | ||
186 | }, | ||
187 | { | ||
188 | fields: [ 'videoChannelId' ] | ||
189 | }, | ||
190 | { | ||
191 | fields: [ 'url' ], | ||
192 | unique: true | ||
193 | } | ||
194 | ] | ||
195 | }) | ||
196 | export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | ||
197 | @CreatedAt | ||
198 | createdAt: Date | ||
199 | |||
200 | @UpdatedAt | ||
201 | updatedAt: Date | ||
202 | |||
203 | @AllowNull(false) | ||
204 | @Is('VideoPlaylistName', value => throwIfNotValid(value, isVideoPlaylistNameValid, 'name')) | ||
205 | @Column | ||
206 | name: string | ||
207 | |||
208 | @AllowNull(true) | ||
209 | @Is('VideoPlaylistDescription', value => throwIfNotValid(value, isVideoPlaylistDescriptionValid, 'description', true)) | ||
210 | @Column | ||
211 | description: string | ||
212 | |||
213 | @AllowNull(false) | ||
214 | @Is('VideoPlaylistPrivacy', value => throwIfNotValid(value, isVideoPlaylistPrivacyValid, 'privacy')) | ||
215 | @Column | ||
216 | privacy: VideoPlaylistPrivacy | ||
217 | |||
218 | @AllowNull(false) | ||
219 | @Is('VideoPlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) | ||
220 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_PLAYLISTS.URL.max)) | ||
221 | url: string | ||
222 | |||
223 | @AllowNull(false) | ||
224 | @Default(DataType.UUIDV4) | ||
225 | @IsUUID(4) | ||
226 | @Column(DataType.UUID) | ||
227 | uuid: string | ||
228 | |||
229 | @AllowNull(false) | ||
230 | @Default(VideoPlaylistType.REGULAR) | ||
231 | @Column | ||
232 | type: VideoPlaylistType | ||
233 | |||
234 | @ForeignKey(() => AccountModel) | ||
235 | @Column | ||
236 | ownerAccountId: number | ||
237 | |||
238 | @BelongsTo(() => AccountModel, { | ||
239 | foreignKey: { | ||
240 | allowNull: false | ||
241 | }, | ||
242 | onDelete: 'CASCADE' | ||
243 | }) | ||
244 | OwnerAccount: AccountModel | ||
245 | |||
246 | @ForeignKey(() => VideoChannelModel) | ||
247 | @Column | ||
248 | videoChannelId: number | ||
249 | |||
250 | @BelongsTo(() => VideoChannelModel, { | ||
251 | foreignKey: { | ||
252 | allowNull: true | ||
253 | }, | ||
254 | onDelete: 'CASCADE' | ||
255 | }) | ||
256 | VideoChannel: VideoChannelModel | ||
257 | |||
258 | @HasMany(() => VideoPlaylistElementModel, { | ||
259 | foreignKey: { | ||
260 | name: 'videoPlaylistId', | ||
261 | allowNull: false | ||
262 | }, | ||
263 | onDelete: 'CASCADE' | ||
264 | }) | ||
265 | VideoPlaylistElements: VideoPlaylistElementModel[] | ||
266 | |||
267 | @HasOne(() => ThumbnailModel, { | ||
268 | |||
269 | foreignKey: { | ||
270 | name: 'videoPlaylistId', | ||
271 | allowNull: true | ||
272 | }, | ||
273 | onDelete: 'CASCADE', | ||
274 | hooks: true | ||
275 | }) | ||
276 | Thumbnail: ThumbnailModel | ||
277 | |||
278 | static listForApi (options: { | ||
279 | followerActorId: number | ||
280 | start: number, | ||
281 | count: number, | ||
282 | sort: string, | ||
283 | type?: VideoPlaylistType, | ||
284 | accountId?: number, | ||
285 | videoChannelId?: number, | ||
286 | privateAndUnlisted?: boolean | ||
287 | }) { | ||
288 | const query = { | ||
289 | offset: options.start, | ||
290 | limit: options.count, | ||
291 | order: getSort(options.sort) | ||
292 | } | ||
293 | |||
294 | const scopes: (string | ScopeOptions)[] = [ | ||
295 | { | ||
296 | method: [ | ||
297 | ScopeNames.AVAILABLE_FOR_LIST, | ||
298 | { | ||
299 | type: options.type, | ||
300 | followerActorId: options.followerActorId, | ||
301 | accountId: options.accountId, | ||
302 | videoChannelId: options.videoChannelId, | ||
303 | privateAndUnlisted: options.privateAndUnlisted | ||
304 | } as AvailableForListOptions | ||
305 | ] | ||
306 | }, | ||
307 | ScopeNames.WITH_VIDEOS_LENGTH, | ||
308 | ScopeNames.WITH_THUMBNAIL | ||
309 | ] | ||
310 | |||
311 | return VideoPlaylistModel | ||
312 | .scope(scopes) | ||
313 | .findAndCountAll(query) | ||
314 | .then(({ rows, count }) => { | ||
315 | return { total: count, data: rows } | ||
316 | }) | ||
317 | } | ||
318 | |||
319 | static listPublicUrlsOfForAP (accountId: number, start: number, count: number) { | ||
320 | const query = { | ||
321 | attributes: [ 'url' ], | ||
322 | offset: start, | ||
323 | limit: count, | ||
324 | where: { | ||
325 | ownerAccountId: accountId, | ||
326 | privacy: VideoPlaylistPrivacy.PUBLIC | ||
327 | } | ||
328 | } | ||
329 | |||
330 | return VideoPlaylistModel.findAndCountAll(query) | ||
331 | .then(({ rows, count }) => { | ||
332 | return { total: count, data: rows.map(p => p.url) } | ||
333 | }) | ||
334 | } | ||
335 | |||
336 | static listPlaylistIdsOf (accountId: number, videoIds: number[]) { | ||
337 | const query = { | ||
338 | attributes: [ 'id' ], | ||
339 | where: { | ||
340 | ownerAccountId: accountId | ||
341 | }, | ||
342 | include: [ | ||
343 | { | ||
344 | attributes: [ 'videoId', 'startTimestamp', 'stopTimestamp' ], | ||
345 | model: VideoPlaylistElementModel.unscoped(), | ||
346 | where: { | ||
347 | videoId: { | ||
348 | [Op.in]: videoIds // FIXME: sequelize ANY seems broken | ||
349 | } | ||
350 | }, | ||
351 | required: true | ||
352 | } | ||
353 | ] | ||
354 | } | ||
355 | |||
356 | return VideoPlaylistModel.findAll(query) | ||
357 | } | ||
358 | |||
359 | static doesPlaylistExist (url: string) { | ||
360 | const query = { | ||
361 | attributes: [], | ||
362 | where: { | ||
363 | url | ||
364 | } | ||
365 | } | ||
366 | |||
367 | return VideoPlaylistModel | ||
368 | .findOne(query) | ||
369 | .then(e => !!e) | ||
370 | } | ||
371 | |||
372 | static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction) { | ||
373 | const where = buildWhereIdOrUUID(id) | ||
374 | |||
375 | const query = { | ||
376 | where, | ||
377 | transaction | ||
378 | } | ||
379 | |||
380 | return VideoPlaylistModel | ||
381 | .scope([ ScopeNames.WITH_ACCOUNT_AND_CHANNEL_SUMMARY, ScopeNames.WITH_VIDEOS_LENGTH, ScopeNames.WITH_THUMBNAIL ]) | ||
382 | .findOne(query) | ||
383 | } | ||
384 | |||
385 | static loadWithAccountAndChannel (id: number | string, transaction: Transaction) { | ||
386 | const where = buildWhereIdOrUUID(id) | ||
387 | |||
388 | const query = { | ||
389 | where, | ||
390 | transaction | ||
391 | } | ||
392 | |||
393 | return VideoPlaylistModel | ||
394 | .scope([ ScopeNames.WITH_ACCOUNT_AND_CHANNEL, ScopeNames.WITH_VIDEOS_LENGTH, ScopeNames.WITH_THUMBNAIL ]) | ||
395 | .findOne(query) | ||
396 | } | ||
397 | |||
398 | static loadByUrlAndPopulateAccount (url: string) { | ||
399 | const query = { | ||
400 | where: { | ||
401 | url | ||
402 | } | ||
403 | } | ||
404 | |||
405 | return VideoPlaylistModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_THUMBNAIL ]).findOne(query) | ||
406 | } | ||
407 | |||
408 | static getPrivacyLabel (privacy: VideoPlaylistPrivacy) { | ||
409 | return VIDEO_PLAYLIST_PRIVACIES[privacy] || 'Unknown' | ||
410 | } | ||
411 | |||
412 | static getTypeLabel (type: VideoPlaylistType) { | ||
413 | return VIDEO_PLAYLIST_TYPES[type] || 'Unknown' | ||
414 | } | ||
415 | |||
416 | static resetPlaylistsOfChannel (videoChannelId: number, transaction: Transaction) { | ||
417 | const query = { | ||
418 | where: { | ||
419 | videoChannelId | ||
420 | }, | ||
421 | transaction | ||
422 | } | ||
423 | |||
424 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) | ||
425 | } | ||
426 | |||
427 | async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) { | ||
428 | thumbnail.videoPlaylistId = this.id | ||
429 | |||
430 | this.Thumbnail = await thumbnail.save({ transaction: t }) | ||
431 | } | ||
432 | |||
433 | hasThumbnail () { | ||
434 | return !!this.Thumbnail | ||
435 | } | ||
436 | |||
437 | generateThumbnailName () { | ||
438 | const extension = '.jpg' | ||
439 | |||
440 | return 'playlist-' + this.uuid + extension | ||
441 | } | ||
442 | |||
443 | getThumbnailUrl () { | ||
444 | if (!this.hasThumbnail()) return null | ||
445 | |||
446 | return WEBSERVER.URL + STATIC_PATHS.THUMBNAILS + this.Thumbnail.filename | ||
447 | } | ||
448 | |||
449 | getThumbnailStaticPath () { | ||
450 | if (!this.hasThumbnail()) return null | ||
451 | |||
452 | return join(STATIC_PATHS.THUMBNAILS, this.Thumbnail.filename) | ||
453 | } | ||
454 | |||
455 | setAsRefreshed () { | ||
456 | this.changed('updatedAt', true) | ||
457 | |||
458 | return this.save() | ||
459 | } | ||
460 | |||
461 | isOwned () { | ||
462 | return this.OwnerAccount.isOwned() | ||
463 | } | ||
464 | |||
465 | isOutdated () { | ||
466 | if (this.isOwned()) return false | ||
467 | |||
468 | return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL) | ||
469 | } | ||
470 | |||
471 | toFormattedJSON (): VideoPlaylist { | ||
472 | return { | ||
473 | id: this.id, | ||
474 | uuid: this.uuid, | ||
475 | isLocal: this.isOwned(), | ||
476 | |||
477 | displayName: this.name, | ||
478 | description: this.description, | ||
479 | privacy: { | ||
480 | id: this.privacy, | ||
481 | label: VideoPlaylistModel.getPrivacyLabel(this.privacy) | ||
482 | }, | ||
483 | |||
484 | thumbnailPath: this.getThumbnailStaticPath(), | ||
485 | |||
486 | type: { | ||
487 | id: this.type, | ||
488 | label: VideoPlaylistModel.getTypeLabel(this.type) | ||
489 | }, | ||
490 | |||
491 | videosLength: this.get('videosLength') as number, | ||
492 | |||
493 | createdAt: this.createdAt, | ||
494 | updatedAt: this.updatedAt, | ||
495 | |||
496 | ownerAccount: this.OwnerAccount.toFormattedSummaryJSON(), | ||
497 | videoChannel: this.VideoChannel ? this.VideoChannel.toFormattedSummaryJSON() : null | ||
498 | } | ||
499 | } | ||
500 | |||
501 | toActivityPubObject (page: number, t: Transaction): Promise<PlaylistObject> { | ||
502 | const handler = (start: number, count: number) => { | ||
503 | return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t) | ||
504 | } | ||
505 | |||
506 | let icon: ActivityIconObject | ||
507 | if (this.hasThumbnail()) { | ||
508 | icon = { | ||
509 | type: 'Image' as 'Image', | ||
510 | url: this.getThumbnailUrl(), | ||
511 | mediaType: 'image/jpeg' as 'image/jpeg', | ||
512 | width: THUMBNAILS_SIZE.width, | ||
513 | height: THUMBNAILS_SIZE.height | ||
514 | } | ||
515 | } | ||
516 | |||
517 | return activityPubCollectionPagination(this.url, handler, page) | ||
518 | .then(o => { | ||
519 | return Object.assign(o, { | ||
520 | type: 'Playlist' as 'Playlist', | ||
521 | name: this.name, | ||
522 | content: this.description, | ||
523 | uuid: this.uuid, | ||
524 | published: this.createdAt.toISOString(), | ||
525 | updated: this.updatedAt.toISOString(), | ||
526 | attributedTo: this.VideoChannel ? [ this.VideoChannel.Actor.url ] : [], | ||
527 | icon | ||
528 | }) | ||
529 | }) | ||
530 | } | ||
531 | } | ||
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index c87f71277..fda2d7cea 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts | |||
@@ -2,7 +2,7 @@ import * as Sequelize from 'sequelize' | |||
2 | import * as Bluebird from 'bluebird' | 2 | import * as Bluebird from 'bluebird' |
3 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
4 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 4 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
5 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 5 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
6 | import { AccountModel } from '../account/account' | 6 | import { AccountModel } from '../account/account' |
7 | import { ActorModel } from '../activitypub/actor' | 7 | import { ActorModel } from '../activitypub/actor' |
8 | import { throwIfNotValid } from '../utils' | 8 | import { throwIfNotValid } from '../utils' |
@@ -14,15 +14,15 @@ enum ScopeNames { | |||
14 | WITH_ACTOR = 'WITH_ACTOR' | 14 | WITH_ACTOR = 'WITH_ACTOR' |
15 | } | 15 | } |
16 | 16 | ||
17 | @Scopes({ | 17 | @Scopes(() => ({ |
18 | [ScopeNames.FULL]: { | 18 | [ScopeNames.FULL]: { |
19 | include: [ | 19 | include: [ |
20 | { | 20 | { |
21 | model: () => ActorModel, | 21 | model: ActorModel, |
22 | required: true | 22 | required: true |
23 | }, | 23 | }, |
24 | { | 24 | { |
25 | model: () => VideoModel, | 25 | model: VideoModel, |
26 | required: true | 26 | required: true |
27 | } | 27 | } |
28 | ] | 28 | ] |
@@ -30,12 +30,12 @@ enum ScopeNames { | |||
30 | [ScopeNames.WITH_ACTOR]: { | 30 | [ScopeNames.WITH_ACTOR]: { |
31 | include: [ | 31 | include: [ |
32 | { | 32 | { |
33 | model: () => ActorModel, | 33 | model: ActorModel, |
34 | required: true | 34 | required: true |
35 | } | 35 | } |
36 | ] | 36 | ] |
37 | } | 37 | } |
38 | }) | 38 | })) |
39 | @Table({ | 39 | @Table({ |
40 | tableName: 'videoShare', | 40 | tableName: 'videoShare', |
41 | indexes: [ | 41 | indexes: [ |
@@ -125,7 +125,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
125 | .then(res => res.map(r => r.Actor)) | 125 | .then(res => res.map(r => r.Actor)) |
126 | } | 126 | } |
127 | 127 | ||
128 | static loadActorsByVideoOwner (actorOwnerId: number, t: Sequelize.Transaction): Bluebird<ActorModel[]> { | 128 | static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Sequelize.Transaction): Bluebird<ActorModel[]> { |
129 | const query = { | 129 | const query = { |
130 | attributes: [], | 130 | attributes: [], |
131 | include: [ | 131 | include: [ |
@@ -200,4 +200,17 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
200 | 200 | ||
201 | return VideoShareModel.findAndCountAll(query) | 201 | return VideoShareModel.findAndCountAll(query) |
202 | } | 202 | } |
203 | |||
204 | static cleanOldSharesOf (videoId: number, beforeUpdatedAt: Date) { | ||
205 | const query = { | ||
206 | where: { | ||
207 | updatedAt: { | ||
208 | [Sequelize.Op.lt]: beforeUpdatedAt | ||
209 | }, | ||
210 | videoId | ||
211 | } | ||
212 | } | ||
213 | |||
214 | return VideoShareModel.destroy(query) | ||
215 | } | ||
203 | } | 216 | } |
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts new file mode 100644 index 000000000..31dc82c54 --- /dev/null +++ b/server/models/video/video-streaming-playlist.ts | |||
@@ -0,0 +1,172 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, HasMany, Is, Model, Table, UpdatedAt, DataType } from 'sequelize-typescript' | ||
2 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | ||
3 | import { throwIfNotValid } from '../utils' | ||
4 | import { VideoModel } from './video' | ||
5 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | ||
6 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | ||
7 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | ||
8 | import { CONSTRAINTS_FIELDS, STATIC_PATHS, P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants' | ||
9 | import { VideoFileModel } from './video-file' | ||
10 | import { join } from 'path' | ||
11 | import { sha1 } from '../../helpers/core-utils' | ||
12 | import { isArrayOf } from '../../helpers/custom-validators/misc' | ||
13 | import { QueryTypes, Op } from 'sequelize' | ||
14 | |||
15 | @Table({ | ||
16 | tableName: 'videoStreamingPlaylist', | ||
17 | indexes: [ | ||
18 | { | ||
19 | fields: [ 'videoId' ] | ||
20 | }, | ||
21 | { | ||
22 | fields: [ 'videoId', 'type' ], | ||
23 | unique: true | ||
24 | }, | ||
25 | { | ||
26 | fields: [ 'p2pMediaLoaderInfohashes' ], | ||
27 | using: 'gin' | ||
28 | } | ||
29 | ] | ||
30 | }) | ||
31 | export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistModel> { | ||
32 | @CreatedAt | ||
33 | createdAt: Date | ||
34 | |||
35 | @UpdatedAt | ||
36 | updatedAt: Date | ||
37 | |||
38 | @AllowNull(false) | ||
39 | @Column | ||
40 | type: VideoStreamingPlaylistType | ||
41 | |||
42 | @AllowNull(false) | ||
43 | @Is('PlaylistUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'playlist url')) | ||
44 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.URL.max)) | ||
45 | playlistUrl: string | ||
46 | |||
47 | @AllowNull(false) | ||
48 | @Is('VideoStreamingPlaylistInfoHashes', value => throwIfNotValid(value, v => isArrayOf(v, isVideoFileInfoHashValid), 'info hashes')) | ||
49 | @Column(DataType.ARRAY(DataType.STRING)) | ||
50 | p2pMediaLoaderInfohashes: string[] | ||
51 | |||
52 | @AllowNull(false) | ||
53 | @Column | ||
54 | p2pMediaLoaderPeerVersion: number | ||
55 | |||
56 | @AllowNull(false) | ||
57 | @Is('VideoStreamingSegmentsSha256Url', value => throwIfNotValid(value, isActivityPubUrlValid, 'segments sha256 url')) | ||
58 | @Column | ||
59 | segmentsSha256Url: string | ||
60 | |||
61 | @ForeignKey(() => VideoModel) | ||
62 | @Column | ||
63 | videoId: number | ||
64 | |||
65 | @BelongsTo(() => VideoModel, { | ||
66 | foreignKey: { | ||
67 | allowNull: false | ||
68 | }, | ||
69 | onDelete: 'CASCADE' | ||
70 | }) | ||
71 | Video: VideoModel | ||
72 | |||
73 | @HasMany(() => VideoRedundancyModel, { | ||
74 | foreignKey: { | ||
75 | allowNull: false | ||
76 | }, | ||
77 | onDelete: 'CASCADE', | ||
78 | hooks: true | ||
79 | }) | ||
80 | RedundancyVideos: VideoRedundancyModel[] | ||
81 | |||
82 | static doesInfohashExist (infoHash: string) { | ||
83 | const query = 'SELECT 1 FROM "videoStreamingPlaylist" WHERE $infoHash = ANY("p2pMediaLoaderInfohashes") LIMIT 1' | ||
84 | const options = { | ||
85 | type: QueryTypes.SELECT as QueryTypes.SELECT, | ||
86 | bind: { infoHash }, | ||
87 | raw: true | ||
88 | } | ||
89 | |||
90 | return VideoModel.sequelize.query<object>(query, options) | ||
91 | .then(results => results.length === 1) | ||
92 | } | ||
93 | |||
94 | static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: VideoFileModel[]) { | ||
95 | const hashes: string[] = [] | ||
96 | |||
97 | // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115 | ||
98 | for (let i = 0; i < videoFiles.length; i++) { | ||
99 | hashes.push(sha1(`${P2P_MEDIA_LOADER_PEER_VERSION}${playlistUrl}+V${i}`)) | ||
100 | } | ||
101 | |||
102 | return hashes | ||
103 | } | ||
104 | |||
105 | static listByIncorrectPeerVersion () { | ||
106 | const query = { | ||
107 | where: { | ||
108 | p2pMediaLoaderPeerVersion: { | ||
109 | [Op.ne]: P2P_MEDIA_LOADER_PEER_VERSION | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | |||
114 | return VideoStreamingPlaylistModel.findAll(query) | ||
115 | } | ||
116 | |||
117 | static loadWithVideo (id: number) { | ||
118 | const options = { | ||
119 | include: [ | ||
120 | { | ||
121 | model: VideoModel.unscoped(), | ||
122 | required: true | ||
123 | } | ||
124 | ] | ||
125 | } | ||
126 | |||
127 | return VideoStreamingPlaylistModel.findByPk(id, options) | ||
128 | } | ||
129 | |||
130 | static getHlsPlaylistFilename (resolution: number) { | ||
131 | return resolution + '.m3u8' | ||
132 | } | ||
133 | |||
134 | static getMasterHlsPlaylistFilename () { | ||
135 | return 'master.m3u8' | ||
136 | } | ||
137 | |||
138 | static getHlsSha256SegmentsFilename () { | ||
139 | return 'segments-sha256.json' | ||
140 | } | ||
141 | |||
142 | static getHlsVideoName (uuid: string, resolution: number) { | ||
143 | return `${uuid}-${resolution}-fragmented.mp4` | ||
144 | } | ||
145 | |||
146 | static getHlsMasterPlaylistStaticPath (videoUUID: string) { | ||
147 | return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) | ||
148 | } | ||
149 | |||
150 | static getHlsPlaylistStaticPath (videoUUID: string, resolution: number) { | ||
151 | return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsPlaylistFilename(resolution)) | ||
152 | } | ||
153 | |||
154 | static getHlsSha256SegmentsStaticPath (videoUUID: string) { | ||
155 | return join(STATIC_PATHS.STREAMING_PLAYLISTS.HLS, videoUUID, VideoStreamingPlaylistModel.getHlsSha256SegmentsFilename()) | ||
156 | } | ||
157 | |||
158 | getStringType () { | ||
159 | if (this.type === VideoStreamingPlaylistType.HLS) return 'hls' | ||
160 | |||
161 | return 'unknown' | ||
162 | } | ||
163 | |||
164 | getVideoRedundancyUrl (baseUrlHttp: string) { | ||
165 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid | ||
166 | } | ||
167 | |||
168 | hasSameUniqueKeysThan (other: VideoStreamingPlaylistModel) { | ||
169 | return this.type === other.type && | ||
170 | this.videoId === other.videoId | ||
171 | } | ||
172 | } | ||
diff --git a/server/models/video/video-views.ts b/server/models/video/video-views.ts index fde5f7056..40db5effd 100644 --- a/server/models/video/video-views.ts +++ b/server/models/video/video-views.ts | |||
@@ -4,6 +4,7 @@ import * as Sequelize from 'sequelize' | |||
4 | 4 | ||
5 | @Table({ | 5 | @Table({ |
6 | tableName: 'videoView', | 6 | tableName: 'videoView', |
7 | updatedAt: false, | ||
7 | indexes: [ | 8 | indexes: [ |
8 | { | 9 | { |
9 | fields: [ 'videoId' ] | 10 | fields: [ 'videoId' ] |
@@ -41,4 +42,18 @@ export class VideoViewModel extends Model<VideoViewModel> { | |||
41 | }) | 42 | }) |
42 | Video: VideoModel | 43 | Video: VideoModel |
43 | 44 | ||
45 | static removeOldRemoteViewsHistory (beforeDate: string) { | ||
46 | const query = { | ||
47 | where: { | ||
48 | startDate: { | ||
49 | [Sequelize.Op.lt]: beforeDate | ||
50 | }, | ||
51 | videoId: { | ||
52 | [Sequelize.Op.in]: Sequelize.literal('(SELECT "id" FROM "video" WHERE "remote" IS TRUE)') | ||
53 | } | ||
54 | } | ||
55 | } | ||
56 | |||
57 | return VideoViewModel.destroy(query) | ||
58 | } | ||
44 | } | 59 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index 80a6c7832..c0a7892a4 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -3,7 +3,18 @@ import { maxBy } from 'lodash' | |||
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import * as parseTorrent from 'parse-torrent' | 4 | import * as parseTorrent from 'parse-torrent' |
5 | import { join } from 'path' | 5 | import { join } from 'path' |
6 | import * as Sequelize from 'sequelize' | 6 | import { |
7 | CountOptions, | ||
8 | FindOptions, | ||
9 | IncludeOptions, | ||
10 | ModelIndexesOptions, | ||
11 | Op, | ||
12 | QueryTypes, | ||
13 | ScopeOptions, | ||
14 | Sequelize, | ||
15 | Transaction, | ||
16 | WhereOptions | ||
17 | } from 'sequelize' | ||
7 | import { | 18 | import { |
8 | AllowNull, | 19 | AllowNull, |
9 | BeforeDestroy, | 20 | BeforeDestroy, |
@@ -16,8 +27,6 @@ import { | |||
16 | ForeignKey, | 27 | ForeignKey, |
17 | HasMany, | 28 | HasMany, |
18 | HasOne, | 29 | HasOne, |
19 | IFindOptions, | ||
20 | IIncludeOptions, | ||
21 | Is, | 30 | Is, |
22 | IsInt, | 31 | IsInt, |
23 | IsUUID, | 32 | IsUUID, |
@@ -45,35 +54,43 @@ import { | |||
45 | isVideoStateValid, | 54 | isVideoStateValid, |
46 | isVideoSupportValid | 55 | isVideoSupportValid |
47 | } from '../../helpers/custom-validators/videos' | 56 | } from '../../helpers/custom-validators/videos' |
48 | import { generateImageFromVideoFile, getVideoFileResolution } from '../../helpers/ffmpeg-utils' | 57 | import { getVideoFileResolution } from '../../helpers/ffmpeg-utils' |
49 | import { logger } from '../../helpers/logger' | 58 | import { logger } from '../../helpers/logger' |
50 | import { getServerActor } from '../../helpers/utils' | 59 | import { getServerActor } from '../../helpers/utils' |
51 | import { | 60 | import { |
52 | ACTIVITY_PUB, | 61 | ACTIVITY_PUB, |
53 | API_VERSION, | 62 | API_VERSION, |
54 | CONFIG, | ||
55 | CONSTRAINTS_FIELDS, | 63 | CONSTRAINTS_FIELDS, |
56 | PREVIEWS_SIZE, | 64 | HLS_REDUNDANCY_DIRECTORY, |
65 | HLS_STREAMING_PLAYLIST_DIRECTORY, | ||
57 | REMOTE_SCHEME, | 66 | REMOTE_SCHEME, |
58 | STATIC_DOWNLOAD_PATHS, | 67 | STATIC_DOWNLOAD_PATHS, |
59 | STATIC_PATHS, | 68 | STATIC_PATHS, |
60 | THUMBNAILS_SIZE, | ||
61 | VIDEO_CATEGORIES, | 69 | VIDEO_CATEGORIES, |
62 | VIDEO_LANGUAGES, | 70 | VIDEO_LANGUAGES, |
63 | VIDEO_LICENCES, | 71 | VIDEO_LICENCES, |
64 | VIDEO_PRIVACIES, | 72 | VIDEO_PRIVACIES, |
65 | VIDEO_STATES | 73 | VIDEO_STATES, |
66 | } from '../../initializers' | 74 | WEBSERVER |
75 | } from '../../initializers/constants' | ||
67 | import { sendDeleteVideo } from '../../lib/activitypub/send' | 76 | import { sendDeleteVideo } from '../../lib/activitypub/send' |
68 | import { AccountModel } from '../account/account' | 77 | import { AccountModel } from '../account/account' |
69 | import { AccountVideoRateModel } from '../account/account-video-rate' | 78 | import { AccountVideoRateModel } from '../account/account-video-rate' |
70 | import { ActorModel } from '../activitypub/actor' | 79 | import { ActorModel } from '../activitypub/actor' |
71 | import { AvatarModel } from '../avatar/avatar' | 80 | import { AvatarModel } from '../avatar/avatar' |
72 | import { ServerModel } from '../server/server' | 81 | import { ServerModel } from '../server/server' |
73 | import { buildBlockedAccountSQL, buildTrigramSearchIndex, createSimilarityAttribute, getVideoSort, throwIfNotValid } from '../utils' | 82 | import { |
83 | buildBlockedAccountSQL, | ||
84 | buildTrigramSearchIndex, | ||
85 | buildWhereIdOrUUID, | ||
86 | createSimilarityAttribute, | ||
87 | getVideoSort, | ||
88 | isOutdated, | ||
89 | throwIfNotValid | ||
90 | } from '../utils' | ||
74 | import { TagModel } from './tag' | 91 | import { TagModel } from './tag' |
75 | import { VideoAbuseModel } from './video-abuse' | 92 | import { VideoAbuseModel } from './video-abuse' |
76 | import { VideoChannelModel } from './video-channel' | 93 | import { ScopeNames as VideoChannelScopeNames, VideoChannelModel } from './video-channel' |
77 | import { VideoCommentModel } from './video-comment' | 94 | import { VideoCommentModel } from './video-comment' |
78 | import { VideoFileModel } from './video-file' | 95 | import { VideoFileModel } from './video-file' |
79 | import { VideoShareModel } from './video-share' | 96 | import { VideoShareModel } from './video-share' |
@@ -91,13 +108,17 @@ import { | |||
91 | videoModelToFormattedDetailsJSON, | 108 | videoModelToFormattedDetailsJSON, |
92 | videoModelToFormattedJSON | 109 | videoModelToFormattedJSON |
93 | } from './video-format-utils' | 110 | } from './video-format-utils' |
94 | import * as validator from 'validator' | ||
95 | import { UserVideoHistoryModel } from '../account/user-video-history' | 111 | import { UserVideoHistoryModel } from '../account/user-video-history' |
96 | import { UserModel } from '../account/user' | 112 | import { UserModel } from '../account/user' |
97 | import { VideoImportModel } from './video-import' | 113 | import { VideoImportModel } from './video-import' |
114 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | ||
115 | import { VideoPlaylistElementModel } from './video-playlist-element' | ||
116 | import { CONFIG } from '../../initializers/config' | ||
117 | import { ThumbnailModel } from './thumbnail' | ||
118 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | ||
98 | 119 | ||
99 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 120 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
100 | const indexes: Sequelize.DefineIndexesOptions[] = [ | 121 | const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ |
101 | buildTrigramSearchIndex('video_name_trigram', 'name'), | 122 | buildTrigramSearchIndex('video_name_trigram', 'name'), |
102 | 123 | ||
103 | { fields: [ 'createdAt' ] }, | 124 | { fields: [ 'createdAt' ] }, |
@@ -106,10 +127,18 @@ const indexes: Sequelize.DefineIndexesOptions[] = [ | |||
106 | { fields: [ 'views' ] }, | 127 | { fields: [ 'views' ] }, |
107 | { fields: [ 'channelId' ] }, | 128 | { fields: [ 'channelId' ] }, |
108 | { | 129 | { |
130 | fields: [ 'originallyPublishedAt' ], | ||
131 | where: { | ||
132 | originallyPublishedAt: { | ||
133 | [Op.ne]: null | ||
134 | } | ||
135 | } | ||
136 | }, | ||
137 | { | ||
109 | fields: [ 'category' ], // We don't care videos with an unknown category | 138 | fields: [ 'category' ], // We don't care videos with an unknown category |
110 | where: { | 139 | where: { |
111 | category: { | 140 | category: { |
112 | [Sequelize.Op.ne]: null | 141 | [Op.ne]: null |
113 | } | 142 | } |
114 | } | 143 | } |
115 | }, | 144 | }, |
@@ -117,7 +146,7 @@ const indexes: Sequelize.DefineIndexesOptions[] = [ | |||
117 | fields: [ 'licence' ], // We don't care videos with an unknown licence | 146 | fields: [ 'licence' ], // We don't care videos with an unknown licence |
118 | where: { | 147 | where: { |
119 | licence: { | 148 | licence: { |
120 | [Sequelize.Op.ne]: null | 149 | [Op.ne]: null |
121 | } | 150 | } |
122 | } | 151 | } |
123 | }, | 152 | }, |
@@ -125,7 +154,7 @@ const indexes: Sequelize.DefineIndexesOptions[] = [ | |||
125 | fields: [ 'language' ], // We don't care videos with an unknown language | 154 | fields: [ 'language' ], // We don't care videos with an unknown language |
126 | where: { | 155 | where: { |
127 | language: { | 156 | language: { |
128 | [Sequelize.Op.ne]: null | 157 | [Op.ne]: null |
129 | } | 158 | } |
130 | } | 159 | } |
131 | }, | 160 | }, |
@@ -159,11 +188,17 @@ export enum ScopeNames { | |||
159 | WITH_FILES = 'WITH_FILES', | 188 | WITH_FILES = 'WITH_FILES', |
160 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', | 189 | WITH_SCHEDULED_UPDATE = 'WITH_SCHEDULED_UPDATE', |
161 | WITH_BLACKLISTED = 'WITH_BLACKLISTED', | 190 | WITH_BLACKLISTED = 'WITH_BLACKLISTED', |
162 | WITH_USER_HISTORY = 'WITH_USER_HISTORY' | 191 | WITH_USER_HISTORY = 'WITH_USER_HISTORY', |
192 | WITH_STREAMING_PLAYLISTS = 'WITH_STREAMING_PLAYLISTS', | ||
193 | WITH_USER_ID = 'WITH_USER_ID', | ||
194 | WITH_THUMBNAILS = 'WITH_THUMBNAILS' | ||
163 | } | 195 | } |
164 | 196 | ||
165 | type ForAPIOptions = { | 197 | type ForAPIOptions = { |
166 | ids: number[] | 198 | ids: number[] |
199 | |||
200 | videoPlaylistId?: number | ||
201 | |||
167 | withFiles?: boolean | 202 | withFiles?: boolean |
168 | } | 203 | } |
169 | 204 | ||
@@ -171,6 +206,9 @@ type AvailableForListIDsOptions = { | |||
171 | serverAccountId: number | 206 | serverAccountId: number |
172 | followerActorId: number | 207 | followerActorId: number |
173 | includeLocalVideos: boolean | 208 | includeLocalVideos: boolean |
209 | |||
210 | withoutId?: boolean | ||
211 | |||
174 | filter?: VideoFilter | 212 | filter?: VideoFilter |
175 | categoryOneOf?: number[] | 213 | categoryOneOf?: number[] |
176 | nsfw?: boolean | 214 | nsfw?: boolean |
@@ -178,72 +216,38 @@ type AvailableForListIDsOptions = { | |||
178 | languageOneOf?: string[] | 216 | languageOneOf?: string[] |
179 | tagsOneOf?: string[] | 217 | tagsOneOf?: string[] |
180 | tagsAllOf?: string[] | 218 | tagsAllOf?: string[] |
219 | |||
181 | withFiles?: boolean | 220 | withFiles?: boolean |
221 | |||
182 | accountId?: number | 222 | accountId?: number |
183 | videoChannelId?: number | 223 | videoChannelId?: number |
224 | |||
225 | videoPlaylistId?: number | ||
226 | |||
184 | trendingDays?: number | 227 | trendingDays?: number |
185 | user?: UserModel, | 228 | user?: UserModel, |
186 | historyOfUser?: UserModel | 229 | historyOfUser?: UserModel |
187 | } | 230 | } |
188 | 231 | ||
189 | @Scopes({ | 232 | @Scopes(() => ({ |
190 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { | 233 | [ ScopeNames.FOR_API ]: (options: ForAPIOptions) => { |
191 | const accountInclude = { | 234 | const query: FindOptions = { |
192 | attributes: [ 'id', 'name' ], | 235 | where: { |
193 | model: AccountModel.unscoped(), | 236 | id: { |
194 | required: true, | 237 | [ Op.in ]: options.ids // FIXME: sequelize ANY seems broken |
195 | include: [ | ||
196 | { | ||
197 | attributes: [ 'id', 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], | ||
198 | model: ActorModel.unscoped(), | ||
199 | required: true, | ||
200 | include: [ | ||
201 | { | ||
202 | attributes: [ 'host' ], | ||
203 | model: ServerModel.unscoped(), | ||
204 | required: false | ||
205 | }, | ||
206 | { | ||
207 | model: AvatarModel.unscoped(), | ||
208 | required: false | ||
209 | } | ||
210 | ] | ||
211 | } | 238 | } |
212 | ] | 239 | }, |
213 | } | ||
214 | |||
215 | const videoChannelInclude = { | ||
216 | attributes: [ 'name', 'description', 'id' ], | ||
217 | model: VideoChannelModel.unscoped(), | ||
218 | required: true, | ||
219 | include: [ | 240 | include: [ |
220 | { | 241 | { |
221 | attributes: [ 'uuid', 'preferredUsername', 'url', 'serverId', 'avatarId' ], | 242 | model: VideoChannelModel.scope({ method: [ VideoChannelScopeNames.SUMMARY, true ] }), |
222 | model: ActorModel.unscoped(), | 243 | required: true |
223 | required: true, | ||
224 | include: [ | ||
225 | { | ||
226 | attributes: [ 'host' ], | ||
227 | model: ServerModel.unscoped(), | ||
228 | required: false | ||
229 | }, | ||
230 | { | ||
231 | model: AvatarModel.unscoped(), | ||
232 | required: false | ||
233 | } | ||
234 | ] | ||
235 | }, | 244 | }, |
236 | accountInclude | 245 | { |
237 | ] | 246 | attributes: [ 'type', 'filename' ], |
238 | } | 247 | model: ThumbnailModel, |
239 | 248 | required: false | |
240 | const query: IFindOptions<VideoModel> = { | ||
241 | where: { | ||
242 | id: { | ||
243 | [ Sequelize.Op.any ]: options.ids | ||
244 | } | 249 | } |
245 | }, | 250 | ] |
246 | include: [ videoChannelInclude ] | ||
247 | } | 251 | } |
248 | 252 | ||
249 | if (options.withFiles === true) { | 253 | if (options.withFiles === true) { |
@@ -253,24 +257,36 @@ type AvailableForListIDsOptions = { | |||
253 | }) | 257 | }) |
254 | } | 258 | } |
255 | 259 | ||
260 | if (options.videoPlaylistId) { | ||
261 | query.include.push({ | ||
262 | model: VideoPlaylistElementModel.unscoped(), | ||
263 | required: true, | ||
264 | where: { | ||
265 | videoPlaylistId: options.videoPlaylistId | ||
266 | } | ||
267 | }) | ||
268 | } | ||
269 | |||
256 | return query | 270 | return query |
257 | }, | 271 | }, |
258 | [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { | 272 | [ ScopeNames.AVAILABLE_FOR_LIST_IDS ]: (options: AvailableForListIDsOptions) => { |
259 | const query: IFindOptions<VideoModel> = { | 273 | const attributes = options.withoutId === true ? [] : [ 'id' ] |
274 | |||
275 | const query: FindOptions = { | ||
260 | raw: true, | 276 | raw: true, |
261 | attributes: [ 'id' ], | 277 | attributes, |
262 | where: { | 278 | where: { |
263 | id: { | 279 | id: { |
264 | [ Sequelize.Op.and ]: [ | 280 | [ Op.and ]: [ |
265 | { | 281 | { |
266 | [ Sequelize.Op.notIn ]: Sequelize.literal( | 282 | [ Op.notIn ]: Sequelize.literal( |
267 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' | 283 | '(SELECT "videoBlacklist"."videoId" FROM "videoBlacklist")' |
268 | ) | 284 | ) |
269 | } | 285 | } |
270 | ] | 286 | ] |
271 | }, | 287 | }, |
272 | channelId: { | 288 | channelId: { |
273 | [ Sequelize.Op.notIn ]: Sequelize.literal( | 289 | [ Op.notIn ]: Sequelize.literal( |
274 | '(' + | 290 | '(' + |
275 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + | 291 | 'SELECT id FROM "videoChannel" WHERE "accountId" IN (' + |
276 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + | 292 | buildBlockedAccountSQL(options.serverAccountId, options.user ? options.user.Account.id : undefined) + |
@@ -288,12 +304,12 @@ type AvailableForListIDsOptions = { | |||
288 | // Always list public videos | 304 | // Always list public videos |
289 | privacy: VideoPrivacy.PUBLIC, | 305 | privacy: VideoPrivacy.PUBLIC, |
290 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding | 306 | // Always list published videos, or videos that are being transcoded but on which we don't want to wait for transcoding |
291 | [ Sequelize.Op.or ]: [ | 307 | [ Op.or ]: [ |
292 | { | 308 | { |
293 | state: VideoState.PUBLISHED | 309 | state: VideoState.PUBLISHED |
294 | }, | 310 | }, |
295 | { | 311 | { |
296 | [ Sequelize.Op.and ]: { | 312 | [ Op.and ]: { |
297 | state: VideoState.TO_TRANSCODE, | 313 | state: VideoState.TO_TRANSCODE, |
298 | waitTranscoding: false | 314 | waitTranscoding: false |
299 | } | 315 | } |
@@ -304,8 +320,21 @@ type AvailableForListIDsOptions = { | |||
304 | Object.assign(query.where, privacyWhere) | 320 | Object.assign(query.where, privacyWhere) |
305 | } | 321 | } |
306 | 322 | ||
323 | if (options.videoPlaylistId) { | ||
324 | query.include.push({ | ||
325 | attributes: [], | ||
326 | model: VideoPlaylistElementModel.unscoped(), | ||
327 | required: true, | ||
328 | where: { | ||
329 | videoPlaylistId: options.videoPlaylistId | ||
330 | } | ||
331 | }) | ||
332 | |||
333 | query.subQuery = false | ||
334 | } | ||
335 | |||
307 | if (options.filter || options.accountId || options.videoChannelId) { | 336 | if (options.filter || options.accountId || options.videoChannelId) { |
308 | const videoChannelInclude: IIncludeOptions = { | 337 | const videoChannelInclude: IncludeOptions = { |
309 | attributes: [], | 338 | attributes: [], |
310 | model: VideoChannelModel.unscoped(), | 339 | model: VideoChannelModel.unscoped(), |
311 | required: true | 340 | required: true |
@@ -318,7 +347,7 @@ type AvailableForListIDsOptions = { | |||
318 | } | 347 | } |
319 | 348 | ||
320 | if (options.filter || options.accountId) { | 349 | if (options.filter || options.accountId) { |
321 | const accountInclude: IIncludeOptions = { | 350 | const accountInclude: IncludeOptions = { |
322 | attributes: [], | 351 | attributes: [], |
323 | model: AccountModel.unscoped(), | 352 | model: AccountModel.unscoped(), |
324 | required: true | 353 | required: true |
@@ -358,8 +387,8 @@ type AvailableForListIDsOptions = { | |||
358 | 387 | ||
359 | // Force actorId to be a number to avoid SQL injections | 388 | // Force actorId to be a number to avoid SQL injections |
360 | const actorIdNumber = parseInt(options.followerActorId.toString(), 10) | 389 | const actorIdNumber = parseInt(options.followerActorId.toString(), 10) |
361 | query.where[ 'id' ][ Sequelize.Op.and ].push({ | 390 | query.where[ 'id' ][ Op.and ].push({ |
362 | [ Sequelize.Op.in ]: Sequelize.literal( | 391 | [ Op.in ]: Sequelize.literal( |
363 | '(' + | 392 | '(' + |
364 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + | 393 | 'SELECT "videoShare"."videoId" AS "id" FROM "videoShare" ' + |
365 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + | 394 | 'INNER JOIN "actorFollow" ON "actorFollow"."targetActorId" = "videoShare"."actorId" ' + |
@@ -378,8 +407,8 @@ type AvailableForListIDsOptions = { | |||
378 | } | 407 | } |
379 | 408 | ||
380 | if (options.withFiles === true) { | 409 | if (options.withFiles === true) { |
381 | query.where[ 'id' ][ Sequelize.Op.and ].push({ | 410 | query.where[ 'id' ][ Op.and ].push({ |
382 | [ Sequelize.Op.in ]: Sequelize.literal( | 411 | [ Op.in ]: Sequelize.literal( |
383 | '(SELECT "videoId" FROM "videoFile")' | 412 | '(SELECT "videoId" FROM "videoFile")' |
384 | ) | 413 | ) |
385 | }) | 414 | }) |
@@ -393,8 +422,8 @@ type AvailableForListIDsOptions = { | |||
393 | } | 422 | } |
394 | 423 | ||
395 | if (options.tagsOneOf) { | 424 | if (options.tagsOneOf) { |
396 | query.where[ 'id' ][ Sequelize.Op.and ].push({ | 425 | query.where[ 'id' ][ Op.and ].push({ |
397 | [ Sequelize.Op.in ]: Sequelize.literal( | 426 | [ Op.in ]: Sequelize.literal( |
398 | '(' + | 427 | '(' + |
399 | 'SELECT "videoId" FROM "videoTag" ' + | 428 | 'SELECT "videoId" FROM "videoTag" ' + |
400 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 429 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
@@ -405,8 +434,8 @@ type AvailableForListIDsOptions = { | |||
405 | } | 434 | } |
406 | 435 | ||
407 | if (options.tagsAllOf) { | 436 | if (options.tagsAllOf) { |
408 | query.where[ 'id' ][ Sequelize.Op.and ].push({ | 437 | query.where[ 'id' ][ Op.and ].push({ |
409 | [ Sequelize.Op.in ]: Sequelize.literal( | 438 | [ Op.in ]: Sequelize.literal( |
410 | '(' + | 439 | '(' + |
411 | 'SELECT "videoId" FROM "videoTag" ' + | 440 | 'SELECT "videoId" FROM "videoTag" ' + |
412 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 441 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
@@ -424,19 +453,19 @@ type AvailableForListIDsOptions = { | |||
424 | 453 | ||
425 | if (options.categoryOneOf) { | 454 | if (options.categoryOneOf) { |
426 | query.where[ 'category' ] = { | 455 | query.where[ 'category' ] = { |
427 | [ Sequelize.Op.or ]: options.categoryOneOf | 456 | [ Op.or ]: options.categoryOneOf |
428 | } | 457 | } |
429 | } | 458 | } |
430 | 459 | ||
431 | if (options.licenceOneOf) { | 460 | if (options.licenceOneOf) { |
432 | query.where[ 'licence' ] = { | 461 | query.where[ 'licence' ] = { |
433 | [ Sequelize.Op.or ]: options.licenceOneOf | 462 | [ Op.or ]: options.licenceOneOf |
434 | } | 463 | } |
435 | } | 464 | } |
436 | 465 | ||
437 | if (options.languageOneOf) { | 466 | if (options.languageOneOf) { |
438 | query.where[ 'language' ] = { | 467 | query.where[ 'language' ] = { |
439 | [ Sequelize.Op.or ]: options.languageOneOf | 468 | [ Op.or ]: options.languageOneOf |
440 | } | 469 | } |
441 | } | 470 | } |
442 | 471 | ||
@@ -463,36 +492,60 @@ type AvailableForListIDsOptions = { | |||
463 | 492 | ||
464 | return query | 493 | return query |
465 | }, | 494 | }, |
495 | [ ScopeNames.WITH_THUMBNAILS ]: { | ||
496 | include: [ | ||
497 | { | ||
498 | model: ThumbnailModel, | ||
499 | required: false | ||
500 | } | ||
501 | ] | ||
502 | }, | ||
503 | [ ScopeNames.WITH_USER_ID ]: { | ||
504 | include: [ | ||
505 | { | ||
506 | attributes: [ 'accountId' ], | ||
507 | model: VideoChannelModel.unscoped(), | ||
508 | required: true, | ||
509 | include: [ | ||
510 | { | ||
511 | attributes: [ 'userId' ], | ||
512 | model: AccountModel.unscoped(), | ||
513 | required: true | ||
514 | } | ||
515 | ] | ||
516 | } | ||
517 | ] | ||
518 | }, | ||
466 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { | 519 | [ ScopeNames.WITH_ACCOUNT_DETAILS ]: { |
467 | include: [ | 520 | include: [ |
468 | { | 521 | { |
469 | model: () => VideoChannelModel.unscoped(), | 522 | model: VideoChannelModel.unscoped(), |
470 | required: true, | 523 | required: true, |
471 | include: [ | 524 | include: [ |
472 | { | 525 | { |
473 | attributes: { | 526 | attributes: { |
474 | exclude: [ 'privateKey', 'publicKey' ] | 527 | exclude: [ 'privateKey', 'publicKey' ] |
475 | }, | 528 | }, |
476 | model: () => ActorModel.unscoped(), | 529 | model: ActorModel.unscoped(), |
477 | required: true, | 530 | required: true, |
478 | include: [ | 531 | include: [ |
479 | { | 532 | { |
480 | attributes: [ 'host' ], | 533 | attributes: [ 'host' ], |
481 | model: () => ServerModel.unscoped(), | 534 | model: ServerModel.unscoped(), |
482 | required: false | 535 | required: false |
483 | }, | 536 | }, |
484 | { | 537 | { |
485 | model: () => AvatarModel.unscoped(), | 538 | model: AvatarModel.unscoped(), |
486 | required: false | 539 | required: false |
487 | } | 540 | } |
488 | ] | 541 | ] |
489 | }, | 542 | }, |
490 | { | 543 | { |
491 | model: () => AccountModel.unscoped(), | 544 | model: AccountModel.unscoped(), |
492 | required: true, | 545 | required: true, |
493 | include: [ | 546 | include: [ |
494 | { | 547 | { |
495 | model: () => ActorModel.unscoped(), | 548 | model: ActorModel.unscoped(), |
496 | attributes: { | 549 | attributes: { |
497 | exclude: [ 'privateKey', 'publicKey' ] | 550 | exclude: [ 'privateKey', 'publicKey' ] |
498 | }, | 551 | }, |
@@ -500,11 +553,11 @@ type AvailableForListIDsOptions = { | |||
500 | include: [ | 553 | include: [ |
501 | { | 554 | { |
502 | attributes: [ 'host' ], | 555 | attributes: [ 'host' ], |
503 | model: () => ServerModel.unscoped(), | 556 | model: ServerModel.unscoped(), |
504 | required: false | 557 | required: false |
505 | }, | 558 | }, |
506 | { | 559 | { |
507 | model: () => AvatarModel.unscoped(), | 560 | model: AvatarModel.unscoped(), |
508 | required: false | 561 | required: false |
509 | } | 562 | } |
510 | ] | 563 | ] |
@@ -516,38 +569,69 @@ type AvailableForListIDsOptions = { | |||
516 | ] | 569 | ] |
517 | }, | 570 | }, |
518 | [ ScopeNames.WITH_TAGS ]: { | 571 | [ ScopeNames.WITH_TAGS ]: { |
519 | include: [ () => TagModel ] | 572 | include: [ TagModel ] |
520 | }, | 573 | }, |
521 | [ ScopeNames.WITH_BLACKLISTED ]: { | 574 | [ ScopeNames.WITH_BLACKLISTED ]: { |
522 | include: [ | 575 | include: [ |
523 | { | 576 | { |
524 | attributes: [ 'id', 'reason' ], | 577 | attributes: [ 'id', 'reason' ], |
525 | model: () => VideoBlacklistModel, | 578 | model: VideoBlacklistModel, |
526 | required: false | 579 | required: false |
527 | } | 580 | } |
528 | ] | 581 | ] |
529 | }, | 582 | }, |
530 | [ ScopeNames.WITH_FILES ]: { | 583 | [ ScopeNames.WITH_FILES ]: (withRedundancies = false) => { |
531 | include: [ | 584 | let subInclude: any[] = [] |
532 | { | 585 | |
533 | model: () => VideoFileModel.unscoped(), | 586 | if (withRedundancies === true) { |
534 | // FIXME: typings | 587 | subInclude = [ |
535 | [ 'separate' as any ]: true, // We may have multiple files, having multiple redundancies so let's separate this join | 588 | { |
536 | required: false, | 589 | attributes: [ 'fileUrl' ], |
537 | include: [ | 590 | model: VideoRedundancyModel.unscoped(), |
538 | { | 591 | required: false |
539 | attributes: [ 'fileUrl' ], | 592 | } |
540 | model: () => VideoRedundancyModel.unscoped(), | 593 | ] |
541 | required: false | 594 | } |
542 | } | 595 | |
543 | ] | 596 | return { |
544 | } | 597 | include: [ |
545 | ] | 598 | { |
599 | model: VideoFileModel.unscoped(), | ||
600 | separate: true, // We may have multiple files, having multiple redundancies so let's separate this join | ||
601 | required: false, | ||
602 | include: subInclude | ||
603 | } | ||
604 | ] | ||
605 | } | ||
606 | }, | ||
607 | [ ScopeNames.WITH_STREAMING_PLAYLISTS ]: (withRedundancies = false) => { | ||
608 | let subInclude: any[] = [] | ||
609 | |||
610 | if (withRedundancies === true) { | ||
611 | subInclude = [ | ||
612 | { | ||
613 | attributes: [ 'fileUrl' ], | ||
614 | model: VideoRedundancyModel.unscoped(), | ||
615 | required: false | ||
616 | } | ||
617 | ] | ||
618 | } | ||
619 | |||
620 | return { | ||
621 | include: [ | ||
622 | { | ||
623 | model: VideoStreamingPlaylistModel.unscoped(), | ||
624 | separate: true, // We may have multiple streaming playlists, having multiple redundancies so let's separate this join | ||
625 | required: false, | ||
626 | include: subInclude | ||
627 | } | ||
628 | ] | ||
629 | } | ||
546 | }, | 630 | }, |
547 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { | 631 | [ ScopeNames.WITH_SCHEDULED_UPDATE ]: { |
548 | include: [ | 632 | include: [ |
549 | { | 633 | { |
550 | model: () => ScheduleVideoUpdateModel.unscoped(), | 634 | model: ScheduleVideoUpdateModel.unscoped(), |
551 | required: false | 635 | required: false |
552 | } | 636 | } |
553 | ] | 637 | ] |
@@ -566,7 +650,7 @@ type AvailableForListIDsOptions = { | |||
566 | ] | 650 | ] |
567 | } | 651 | } |
568 | } | 652 | } |
569 | }) | 653 | })) |
570 | @Table({ | 654 | @Table({ |
571 | tableName: 'video', | 655 | tableName: 'video', |
572 | indexes | 656 | indexes |
@@ -586,19 +670,19 @@ export class VideoModel extends Model<VideoModel> { | |||
586 | 670 | ||
587 | @AllowNull(true) | 671 | @AllowNull(true) |
588 | @Default(null) | 672 | @Default(null) |
589 | @Is('VideoCategory', value => throwIfNotValid(value, isVideoCategoryValid, 'category')) | 673 | @Is('VideoCategory', value => throwIfNotValid(value, isVideoCategoryValid, 'category', true)) |
590 | @Column | 674 | @Column |
591 | category: number | 675 | category: number |
592 | 676 | ||
593 | @AllowNull(true) | 677 | @AllowNull(true) |
594 | @Default(null) | 678 | @Default(null) |
595 | @Is('VideoLicence', value => throwIfNotValid(value, isVideoLicenceValid, 'licence')) | 679 | @Is('VideoLicence', value => throwIfNotValid(value, isVideoLicenceValid, 'licence', true)) |
596 | @Column | 680 | @Column |
597 | licence: number | 681 | licence: number |
598 | 682 | ||
599 | @AllowNull(true) | 683 | @AllowNull(true) |
600 | @Default(null) | 684 | @Default(null) |
601 | @Is('VideoLanguage', value => throwIfNotValid(value, isVideoLanguageValid, 'language')) | 685 | @Is('VideoLanguage', value => throwIfNotValid(value, isVideoLanguageValid, 'language', true)) |
602 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.LANGUAGE.max)) | 686 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.LANGUAGE.max)) |
603 | language: string | 687 | language: string |
604 | 688 | ||
@@ -614,13 +698,13 @@ export class VideoModel extends Model<VideoModel> { | |||
614 | 698 | ||
615 | @AllowNull(true) | 699 | @AllowNull(true) |
616 | @Default(null) | 700 | @Default(null) |
617 | @Is('VideoDescription', value => throwIfNotValid(value, isVideoDescriptionValid, 'description')) | 701 | @Is('VideoDescription', value => throwIfNotValid(value, isVideoDescriptionValid, 'description', true)) |
618 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max)) | 702 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.DESCRIPTION.max)) |
619 | description: string | 703 | description: string |
620 | 704 | ||
621 | @AllowNull(true) | 705 | @AllowNull(true) |
622 | @Default(null) | 706 | @Default(null) |
623 | @Is('VideoSupport', value => throwIfNotValid(value, isVideoSupportValid, 'support')) | 707 | @Is('VideoSupport', value => throwIfNotValid(value, isVideoSupportValid, 'support', true)) |
624 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.SUPPORT.max)) | 708 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEOS.SUPPORT.max)) |
625 | support: string | 709 | support: string |
626 | 710 | ||
@@ -665,6 +749,10 @@ export class VideoModel extends Model<VideoModel> { | |||
665 | 749 | ||
666 | @AllowNull(false) | 750 | @AllowNull(false) |
667 | @Column | 751 | @Column |
752 | downloadEnabled: boolean | ||
753 | |||
754 | @AllowNull(false) | ||
755 | @Column | ||
668 | waitTranscoding: boolean | 756 | waitTranscoding: boolean |
669 | 757 | ||
670 | @AllowNull(false) | 758 | @AllowNull(false) |
@@ -680,10 +768,15 @@ export class VideoModel extends Model<VideoModel> { | |||
680 | updatedAt: Date | 768 | updatedAt: Date |
681 | 769 | ||
682 | @AllowNull(false) | 770 | @AllowNull(false) |
683 | @Default(Sequelize.NOW) | 771 | @Default(DataType.NOW) |
684 | @Column | 772 | @Column |
685 | publishedAt: Date | 773 | publishedAt: Date |
686 | 774 | ||
775 | @AllowNull(true) | ||
776 | @Default(null) | ||
777 | @Column | ||
778 | originallyPublishedAt: Date | ||
779 | |||
687 | @ForeignKey(() => VideoChannelModel) | 780 | @ForeignKey(() => VideoChannelModel) |
688 | @Column | 781 | @Column |
689 | channelId: number | 782 | channelId: number |
@@ -703,6 +796,25 @@ export class VideoModel extends Model<VideoModel> { | |||
703 | }) | 796 | }) |
704 | Tags: TagModel[] | 797 | Tags: TagModel[] |
705 | 798 | ||
799 | @HasMany(() => ThumbnailModel, { | ||
800 | foreignKey: { | ||
801 | name: 'videoId', | ||
802 | allowNull: true | ||
803 | }, | ||
804 | hooks: true, | ||
805 | onDelete: 'cascade' | ||
806 | }) | ||
807 | Thumbnails: ThumbnailModel[] | ||
808 | |||
809 | @HasMany(() => VideoPlaylistElementModel, { | ||
810 | foreignKey: { | ||
811 | name: 'videoId', | ||
812 | allowNull: false | ||
813 | }, | ||
814 | onDelete: 'cascade' | ||
815 | }) | ||
816 | VideoPlaylistElements: VideoPlaylistElementModel[] | ||
817 | |||
706 | @HasMany(() => VideoAbuseModel, { | 818 | @HasMany(() => VideoAbuseModel, { |
707 | foreignKey: { | 819 | foreignKey: { |
708 | name: 'videoId', | 820 | name: 'videoId', |
@@ -722,6 +834,16 @@ export class VideoModel extends Model<VideoModel> { | |||
722 | }) | 834 | }) |
723 | VideoFiles: VideoFileModel[] | 835 | VideoFiles: VideoFileModel[] |
724 | 836 | ||
837 | @HasMany(() => VideoStreamingPlaylistModel, { | ||
838 | foreignKey: { | ||
839 | name: 'videoId', | ||
840 | allowNull: false | ||
841 | }, | ||
842 | hooks: true, | ||
843 | onDelete: 'cascade' | ||
844 | }) | ||
845 | VideoStreamingPlaylists: VideoStreamingPlaylistModel[] | ||
846 | |||
725 | @HasMany(() => VideoShareModel, { | 847 | @HasMany(() => VideoShareModel, { |
726 | foreignKey: { | 848 | foreignKey: { |
727 | name: 'videoId', | 849 | name: 'videoId', |
@@ -833,20 +955,19 @@ export class VideoModel extends Model<VideoModel> { | |||
833 | 955 | ||
834 | logger.info('Removing files of video %s.', instance.url) | 956 | logger.info('Removing files of video %s.', instance.url) |
835 | 957 | ||
836 | tasks.push(instance.removeThumbnail()) | ||
837 | |||
838 | if (instance.isOwned()) { | 958 | if (instance.isOwned()) { |
839 | if (!Array.isArray(instance.VideoFiles)) { | 959 | if (!Array.isArray(instance.VideoFiles)) { |
840 | instance.VideoFiles = await instance.$get('VideoFiles') as VideoFileModel[] | 960 | instance.VideoFiles = await instance.$get('VideoFiles') as VideoFileModel[] |
841 | } | 961 | } |
842 | 962 | ||
843 | tasks.push(instance.removePreview()) | ||
844 | |||
845 | // Remove physical files and torrents | 963 | // Remove physical files and torrents |
846 | instance.VideoFiles.forEach(file => { | 964 | instance.VideoFiles.forEach(file => { |
847 | tasks.push(instance.removeFile(file)) | 965 | tasks.push(instance.removeFile(file)) |
848 | tasks.push(instance.removeTorrent(file)) | 966 | tasks.push(instance.removeTorrent(file)) |
849 | }) | 967 | }) |
968 | |||
969 | // Remove playlists file | ||
970 | tasks.push(instance.removeStreamingPlaylist()) | ||
850 | } | 971 | } |
851 | 972 | ||
852 | // Do not wait video deletion because we could be in a transaction | 973 | // Do not wait video deletion because we could be in a transaction |
@@ -858,10 +979,6 @@ export class VideoModel extends Model<VideoModel> { | |||
858 | return undefined | 979 | return undefined |
859 | } | 980 | } |
860 | 981 | ||
861 | static list () { | ||
862 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll() | ||
863 | } | ||
864 | |||
865 | static listLocal () { | 982 | static listLocal () { |
866 | const query = { | 983 | const query = { |
867 | where: { | 984 | where: { |
@@ -869,7 +986,11 @@ export class VideoModel extends Model<VideoModel> { | |||
869 | } | 986 | } |
870 | } | 987 | } |
871 | 988 | ||
872 | return VideoModel.scope(ScopeNames.WITH_FILES).findAll(query) | 989 | return VideoModel.scope([ |
990 | ScopeNames.WITH_FILES, | ||
991 | ScopeNames.WITH_STREAMING_PLAYLISTS, | ||
992 | ScopeNames.WITH_THUMBNAILS | ||
993 | ]).findAll(query) | ||
873 | } | 994 | } |
874 | 995 | ||
875 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { | 996 | static listAllAndSharedByActorForOutbox (actorId: number, start: number, count: number) { |
@@ -892,12 +1013,12 @@ export class VideoModel extends Model<VideoModel> { | |||
892 | distinct: true, | 1013 | distinct: true, |
893 | offset: start, | 1014 | offset: start, |
894 | limit: count, | 1015 | limit: count, |
895 | order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ]), | 1016 | order: getVideoSort('createdAt', [ 'Tags', 'name', 'ASC' ] as any), // FIXME: sequelize typings |
896 | where: { | 1017 | where: { |
897 | id: { | 1018 | id: { |
898 | [ Sequelize.Op.in ]: Sequelize.literal('(' + rawQuery + ')') | 1019 | [ Op.in ]: Sequelize.literal('(' + rawQuery + ')') |
899 | }, | 1020 | }, |
900 | [ Sequelize.Op.or ]: [ | 1021 | [ Op.or ]: [ |
901 | { privacy: VideoPrivacy.PUBLIC }, | 1022 | { privacy: VideoPrivacy.PUBLIC }, |
902 | { privacy: VideoPrivacy.UNLISTED } | 1023 | { privacy: VideoPrivacy.UNLISTED } |
903 | ] | 1024 | ] |
@@ -914,10 +1035,10 @@ export class VideoModel extends Model<VideoModel> { | |||
914 | required: false, | 1035 | required: false, |
915 | // We only want videos shared by this actor | 1036 | // We only want videos shared by this actor |
916 | where: { | 1037 | where: { |
917 | [ Sequelize.Op.and ]: [ | 1038 | [ Op.and ]: [ |
918 | { | 1039 | { |
919 | id: { | 1040 | id: { |
920 | [ Sequelize.Op.not ]: null | 1041 | [ Op.not ]: null |
921 | } | 1042 | } |
922 | }, | 1043 | }, |
923 | { | 1044 | { |
@@ -961,9 +1082,8 @@ export class VideoModel extends Model<VideoModel> { | |||
961 | } | 1082 | } |
962 | 1083 | ||
963 | return Bluebird.all([ | 1084 | return Bluebird.all([ |
964 | // FIXME: typing issue | 1085 | VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findAll(query), |
965 | VideoModel.findAll(query as any), | 1086 | VideoModel.sequelize.query<{ total: string }>(rawCountQuery, { type: QueryTypes.SELECT }) |
966 | VideoModel.sequelize.query(rawCountQuery, { type: Sequelize.QueryTypes.SELECT }) | ||
967 | ]).then(([ rows, totals ]) => { | 1087 | ]).then(([ rows, totals ]) => { |
968 | // totals: totalVideos + totalVideoShares | 1088 | // totals: totalVideos + totalVideoShares |
969 | let totalVideos = 0 | 1089 | let totalVideos = 0 |
@@ -980,43 +1100,49 @@ export class VideoModel extends Model<VideoModel> { | |||
980 | } | 1100 | } |
981 | 1101 | ||
982 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { | 1102 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { |
983 | const query: IFindOptions<VideoModel> = { | 1103 | function buildBaseQuery (): FindOptions { |
984 | offset: start, | 1104 | return { |
985 | limit: count, | 1105 | offset: start, |
986 | order: getVideoSort(sort), | 1106 | limit: count, |
987 | include: [ | 1107 | order: getVideoSort(sort), |
988 | { | 1108 | include: [ |
989 | model: VideoChannelModel, | 1109 | { |
990 | required: true, | 1110 | model: VideoChannelModel, |
991 | include: [ | 1111 | required: true, |
992 | { | 1112 | include: [ |
993 | model: AccountModel, | 1113 | { |
994 | where: { | 1114 | model: AccountModel, |
995 | id: accountId | 1115 | where: { |
996 | }, | 1116 | id: accountId |
997 | required: true | 1117 | }, |
998 | } | 1118 | required: true |
999 | ] | 1119 | } |
1000 | }, | 1120 | ] |
1001 | { | 1121 | } |
1002 | model: ScheduleVideoUpdateModel, | 1122 | ] |
1003 | required: false | 1123 | } |
1004 | }, | ||
1005 | { | ||
1006 | model: VideoBlacklistModel, | ||
1007 | required: false | ||
1008 | } | ||
1009 | ] | ||
1010 | } | 1124 | } |
1011 | 1125 | ||
1126 | const countQuery = buildBaseQuery() | ||
1127 | const findQuery = buildBaseQuery() | ||
1128 | |||
1129 | const findScopes = [ | ||
1130 | ScopeNames.WITH_SCHEDULED_UPDATE, | ||
1131 | ScopeNames.WITH_BLACKLISTED, | ||
1132 | ScopeNames.WITH_THUMBNAILS | ||
1133 | ] | ||
1134 | |||
1012 | if (withFiles === true) { | 1135 | if (withFiles === true) { |
1013 | query.include.push({ | 1136 | findQuery.include.push({ |
1014 | model: VideoFileModel.unscoped(), | 1137 | model: VideoFileModel.unscoped(), |
1015 | required: true | 1138 | required: true |
1016 | }) | 1139 | }) |
1017 | } | 1140 | } |
1018 | 1141 | ||
1019 | return VideoModel.findAndCountAll(query).then(({ rows, count }) => { | 1142 | return Promise.all([ |
1143 | VideoModel.count(countQuery), | ||
1144 | VideoModel.scope(findScopes).findAll(findQuery) | ||
1145 | ]).then(([ count, rows ]) => { | ||
1020 | return { | 1146 | return { |
1021 | data: rows, | 1147 | data: rows, |
1022 | total: count | 1148 | total: count |
@@ -1040,6 +1166,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1040 | accountId?: number, | 1166 | accountId?: number, |
1041 | videoChannelId?: number, | 1167 | videoChannelId?: number, |
1042 | followerActorId?: number | 1168 | followerActorId?: number |
1169 | videoPlaylistId?: number, | ||
1043 | trendingDays?: number, | 1170 | trendingDays?: number, |
1044 | user?: UserModel, | 1171 | user?: UserModel, |
1045 | historyOfUser?: UserModel | 1172 | historyOfUser?: UserModel |
@@ -1048,7 +1175,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1048 | throw new Error('Try to filter all-local but no user has not the see all videos right') | 1175 | throw new Error('Try to filter all-local but no user has not the see all videos right') |
1049 | } | 1176 | } |
1050 | 1177 | ||
1051 | const query: IFindOptions<VideoModel> = { | 1178 | const query: FindOptions = { |
1052 | offset: options.start, | 1179 | offset: options.start, |
1053 | limit: options.count, | 1180 | limit: options.count, |
1054 | order: getVideoSort(options.sort) | 1181 | order: getVideoSort(options.sort) |
@@ -1079,6 +1206,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1079 | withFiles: options.withFiles, | 1206 | withFiles: options.withFiles, |
1080 | accountId: options.accountId, | 1207 | accountId: options.accountId, |
1081 | videoChannelId: options.videoChannelId, | 1208 | videoChannelId: options.videoChannelId, |
1209 | videoPlaylistId: options.videoPlaylistId, | ||
1082 | includeLocalVideos: options.includeLocalVideos, | 1210 | includeLocalVideos: options.includeLocalVideos, |
1083 | user: options.user, | 1211 | user: options.user, |
1084 | historyOfUser: options.historyOfUser, | 1212 | historyOfUser: options.historyOfUser, |
@@ -1096,6 +1224,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1096 | sort?: string | 1224 | sort?: string |
1097 | startDate?: string // ISO 8601 | 1225 | startDate?: string // ISO 8601 |
1098 | endDate?: string // ISO 8601 | 1226 | endDate?: string // ISO 8601 |
1227 | originallyPublishedStartDate?: string | ||
1228 | originallyPublishedEndDate?: string | ||
1099 | nsfw?: boolean | 1229 | nsfw?: boolean |
1100 | categoryOneOf?: number[] | 1230 | categoryOneOf?: number[] |
1101 | licenceOneOf?: number[] | 1231 | licenceOneOf?: number[] |
@@ -1112,17 +1242,26 @@ export class VideoModel extends Model<VideoModel> { | |||
1112 | if (options.startDate || options.endDate) { | 1242 | if (options.startDate || options.endDate) { |
1113 | const publishedAtRange = {} | 1243 | const publishedAtRange = {} |
1114 | 1244 | ||
1115 | if (options.startDate) publishedAtRange[ Sequelize.Op.gte ] = options.startDate | 1245 | if (options.startDate) publishedAtRange[ Op.gte ] = options.startDate |
1116 | if (options.endDate) publishedAtRange[ Sequelize.Op.lte ] = options.endDate | 1246 | if (options.endDate) publishedAtRange[ Op.lte ] = options.endDate |
1117 | 1247 | ||
1118 | whereAnd.push({ publishedAt: publishedAtRange }) | 1248 | whereAnd.push({ publishedAt: publishedAtRange }) |
1119 | } | 1249 | } |
1120 | 1250 | ||
1251 | if (options.originallyPublishedStartDate || options.originallyPublishedEndDate) { | ||
1252 | const originallyPublishedAtRange = {} | ||
1253 | |||
1254 | if (options.originallyPublishedStartDate) originallyPublishedAtRange[ Op.gte ] = options.originallyPublishedStartDate | ||
1255 | if (options.originallyPublishedEndDate) originallyPublishedAtRange[ Op.lte ] = options.originallyPublishedEndDate | ||
1256 | |||
1257 | whereAnd.push({ originallyPublishedAt: originallyPublishedAtRange }) | ||
1258 | } | ||
1259 | |||
1121 | if (options.durationMin || options.durationMax) { | 1260 | if (options.durationMin || options.durationMax) { |
1122 | const durationRange = {} | 1261 | const durationRange = {} |
1123 | 1262 | ||
1124 | if (options.durationMin) durationRange[ Sequelize.Op.gte ] = options.durationMin | 1263 | if (options.durationMin) durationRange[ Op.gte ] = options.durationMin |
1125 | if (options.durationMax) durationRange[ Sequelize.Op.lte ] = options.durationMax | 1264 | if (options.durationMax) durationRange[ Op.lte ] = options.durationMax |
1126 | 1265 | ||
1127 | whereAnd.push({ duration: durationRange }) | 1266 | whereAnd.push({ duration: durationRange }) |
1128 | } | 1267 | } |
@@ -1134,7 +1273,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1134 | whereAnd.push( | 1273 | whereAnd.push( |
1135 | { | 1274 | { |
1136 | id: { | 1275 | id: { |
1137 | [ Sequelize.Op.in ]: Sequelize.literal( | 1276 | [ Op.in ]: Sequelize.literal( |
1138 | '(' + | 1277 | '(' + |
1139 | 'SELECT "video"."id" FROM "video" ' + | 1278 | 'SELECT "video"."id" FROM "video" ' + |
1140 | 'WHERE ' + | 1279 | 'WHERE ' + |
@@ -1160,7 +1299,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1160 | ) | 1299 | ) |
1161 | } | 1300 | } |
1162 | 1301 | ||
1163 | const query: IFindOptions<VideoModel> = { | 1302 | const query: FindOptions = { |
1164 | attributes: { | 1303 | attributes: { |
1165 | include: attributesInclude | 1304 | include: attributesInclude |
1166 | }, | 1305 | }, |
@@ -1168,7 +1307,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1168 | limit: options.count, | 1307 | limit: options.count, |
1169 | order: getVideoSort(options.sort), | 1308 | order: getVideoSort(options.sort), |
1170 | where: { | 1309 | where: { |
1171 | [ Sequelize.Op.and ]: whereAnd | 1310 | [ Op.and ]: whereAnd |
1172 | } | 1311 | } |
1173 | } | 1312 | } |
1174 | 1313 | ||
@@ -1190,18 +1329,32 @@ export class VideoModel extends Model<VideoModel> { | |||
1190 | return VideoModel.getAvailableForApi(query, queryOptions) | 1329 | return VideoModel.getAvailableForApi(query, queryOptions) |
1191 | } | 1330 | } |
1192 | 1331 | ||
1193 | static load (id: number | string, t?: Sequelize.Transaction) { | 1332 | static load (id: number | string, t?: Transaction) { |
1194 | const where = VideoModel.buildWhereIdOrUUID(id) | 1333 | const where = buildWhereIdOrUUID(id) |
1195 | const options = { | 1334 | const options = { |
1196 | where, | 1335 | where, |
1197 | transaction: t | 1336 | transaction: t |
1198 | } | 1337 | } |
1199 | 1338 | ||
1200 | return VideoModel.findOne(options) | 1339 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1201 | } | 1340 | } |
1202 | 1341 | ||
1203 | static loadOnlyId (id: number | string, t?: Sequelize.Transaction) { | 1342 | static loadWithRights (id: number | string, t?: Transaction) { |
1204 | const where = VideoModel.buildWhereIdOrUUID(id) | 1343 | const where = buildWhereIdOrUUID(id) |
1344 | const options = { | ||
1345 | where, | ||
1346 | transaction: t | ||
1347 | } | ||
1348 | |||
1349 | return VideoModel.scope([ | ||
1350 | ScopeNames.WITH_BLACKLISTED, | ||
1351 | ScopeNames.WITH_USER_ID, | ||
1352 | ScopeNames.WITH_THUMBNAILS | ||
1353 | ]).findOne(options) | ||
1354 | } | ||
1355 | |||
1356 | static loadOnlyId (id: number | string, t?: Transaction) { | ||
1357 | const where = buildWhereIdOrUUID(id) | ||
1205 | 1358 | ||
1206 | const options = { | 1359 | const options = { |
1207 | attributes: [ 'id' ], | 1360 | attributes: [ 'id' ], |
@@ -1209,12 +1362,15 @@ export class VideoModel extends Model<VideoModel> { | |||
1209 | transaction: t | 1362 | transaction: t |
1210 | } | 1363 | } |
1211 | 1364 | ||
1212 | return VideoModel.findOne(options) | 1365 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1213 | } | 1366 | } |
1214 | 1367 | ||
1215 | static loadWithFile (id: number, t?: Sequelize.Transaction, logging?: boolean) { | 1368 | static loadWithFiles (id: number, t?: Transaction, logging?: boolean) { |
1216 | return VideoModel.scope(ScopeNames.WITH_FILES) | 1369 | return VideoModel.scope([ |
1217 | .findById(id, { transaction: t, logging }) | 1370 | ScopeNames.WITH_FILES, |
1371 | ScopeNames.WITH_STREAMING_PLAYLISTS, | ||
1372 | ScopeNames.WITH_THUMBNAILS | ||
1373 | ]).findByPk(id, { transaction: t, logging }) | ||
1218 | } | 1374 | } |
1219 | 1375 | ||
1220 | static loadByUUIDWithFile (uuid: string) { | 1376 | static loadByUUIDWithFile (uuid: string) { |
@@ -1224,52 +1380,85 @@ export class VideoModel extends Model<VideoModel> { | |||
1224 | } | 1380 | } |
1225 | } | 1381 | } |
1226 | 1382 | ||
1227 | return VideoModel | 1383 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1228 | .scope([ ScopeNames.WITH_FILES ]) | ||
1229 | .findOne(options) | ||
1230 | } | 1384 | } |
1231 | 1385 | ||
1232 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | 1386 | static loadByUrl (url: string, transaction?: Transaction) { |
1233 | const query: IFindOptions<VideoModel> = { | 1387 | const query: FindOptions = { |
1234 | where: { | 1388 | where: { |
1235 | url | 1389 | url |
1236 | }, | 1390 | }, |
1237 | transaction | 1391 | transaction |
1238 | } | 1392 | } |
1239 | 1393 | ||
1240 | return VideoModel.findOne(query) | 1394 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) |
1241 | } | 1395 | } |
1242 | 1396 | ||
1243 | static loadByUrlAndPopulateAccount (url: string, transaction?: Sequelize.Transaction) { | 1397 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) { |
1244 | const query: IFindOptions<VideoModel> = { | 1398 | const query: FindOptions = { |
1245 | where: { | 1399 | where: { |
1246 | url | 1400 | url |
1247 | }, | 1401 | }, |
1248 | transaction | 1402 | transaction |
1249 | } | 1403 | } |
1250 | 1404 | ||
1251 | return VideoModel.scope([ ScopeNames.WITH_ACCOUNT_DETAILS, ScopeNames.WITH_FILES ]).findOne(query) | 1405 | return VideoModel.scope([ |
1406 | ScopeNames.WITH_ACCOUNT_DETAILS, | ||
1407 | ScopeNames.WITH_FILES, | ||
1408 | ScopeNames.WITH_STREAMING_PLAYLISTS, | ||
1409 | ScopeNames.WITH_THUMBNAILS | ||
1410 | ]).findOne(query) | ||
1252 | } | 1411 | } |
1253 | 1412 | ||
1254 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Sequelize.Transaction, userId?: number) { | 1413 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number) { |
1255 | const where = VideoModel.buildWhereIdOrUUID(id) | 1414 | const where = buildWhereIdOrUUID(id) |
1256 | 1415 | ||
1257 | const options = { | 1416 | const options = { |
1258 | order: [ [ 'Tags', 'name', 'ASC' ] ], | 1417 | order: [ [ 'Tags', 'name', 'ASC' ] ] as any, |
1259 | where, | 1418 | where, |
1260 | transaction: t | 1419 | transaction: t |
1261 | } | 1420 | } |
1262 | 1421 | ||
1263 | const scopes = [ | 1422 | const scopes: (string | ScopeOptions)[] = [ |
1264 | ScopeNames.WITH_TAGS, | 1423 | ScopeNames.WITH_TAGS, |
1265 | ScopeNames.WITH_BLACKLISTED, | 1424 | ScopeNames.WITH_BLACKLISTED, |
1425 | ScopeNames.WITH_ACCOUNT_DETAILS, | ||
1426 | ScopeNames.WITH_SCHEDULED_UPDATE, | ||
1266 | ScopeNames.WITH_FILES, | 1427 | ScopeNames.WITH_FILES, |
1428 | ScopeNames.WITH_STREAMING_PLAYLISTS, | ||
1429 | ScopeNames.WITH_THUMBNAILS | ||
1430 | ] | ||
1431 | |||
1432 | if (userId) { | ||
1433 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) | ||
1434 | } | ||
1435 | |||
1436 | return VideoModel | ||
1437 | .scope(scopes) | ||
1438 | .findOne(options) | ||
1439 | } | ||
1440 | |||
1441 | static loadForGetAPI (id: number | string, t?: Transaction, userId?: number) { | ||
1442 | const where = buildWhereIdOrUUID(id) | ||
1443 | |||
1444 | const options = { | ||
1445 | order: [ [ 'Tags', 'name', 'ASC' ] ] as any, // FIXME: sequelize typings | ||
1446 | where, | ||
1447 | transaction: t | ||
1448 | } | ||
1449 | |||
1450 | const scopes: (string | ScopeOptions)[] = [ | ||
1451 | ScopeNames.WITH_TAGS, | ||
1452 | ScopeNames.WITH_BLACKLISTED, | ||
1267 | ScopeNames.WITH_ACCOUNT_DETAILS, | 1453 | ScopeNames.WITH_ACCOUNT_DETAILS, |
1268 | ScopeNames.WITH_SCHEDULED_UPDATE | 1454 | ScopeNames.WITH_SCHEDULED_UPDATE, |
1455 | ScopeNames.WITH_THUMBNAILS, | ||
1456 | { method: [ ScopeNames.WITH_FILES, true ] }, | ||
1457 | { method: [ ScopeNames.WITH_STREAMING_PLAYLISTS, true ] } | ||
1269 | ] | 1458 | ] |
1270 | 1459 | ||
1271 | if (userId) { | 1460 | if (userId) { |
1272 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] } as any) // FIXME: typings | 1461 | scopes.push({ method: [ ScopeNames.WITH_USER_HISTORY, userId ] }) |
1273 | } | 1462 | } |
1274 | 1463 | ||
1275 | return VideoModel | 1464 | return VideoModel |
@@ -1317,7 +1506,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1317 | 'LIMIT 1' | 1506 | 'LIMIT 1' |
1318 | 1507 | ||
1319 | const options = { | 1508 | const options = { |
1320 | type: Sequelize.QueryTypes.SELECT, | 1509 | type: QueryTypes.SELECT, |
1321 | bind: { followerActorId, videoId }, | 1510 | bind: { followerActorId, videoId }, |
1322 | raw: true | 1511 | raw: true |
1323 | } | 1512 | } |
@@ -1334,17 +1523,18 @@ export class VideoModel extends Model<VideoModel> { | |||
1334 | const scopeOptions: AvailableForListIDsOptions = { | 1523 | const scopeOptions: AvailableForListIDsOptions = { |
1335 | serverAccountId: serverActor.Account.id, | 1524 | serverAccountId: serverActor.Account.id, |
1336 | followerActorId, | 1525 | followerActorId, |
1337 | includeLocalVideos: true | 1526 | includeLocalVideos: true, |
1527 | withoutId: true // Don't break aggregation | ||
1338 | } | 1528 | } |
1339 | 1529 | ||
1340 | const query: IFindOptions<VideoModel> = { | 1530 | const query: FindOptions = { |
1341 | attributes: [ field ], | 1531 | attributes: [ field ], |
1342 | limit: count, | 1532 | limit: count, |
1343 | group: field, | 1533 | group: field, |
1344 | having: Sequelize.where(Sequelize.fn('COUNT', Sequelize.col(field)), { | 1534 | having: Sequelize.where( |
1345 | [ Sequelize.Op.gte ]: threshold | 1535 | Sequelize.fn('COUNT', Sequelize.col(field)), { [ Op.gte ]: threshold } |
1346 | }) as any, // FIXME: typings | 1536 | ), |
1347 | order: [ this.sequelize.random() ] | 1537 | order: [ (this.sequelize as any).random() ] |
1348 | } | 1538 | } |
1349 | 1539 | ||
1350 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) | 1540 | return VideoModel.scope({ method: [ ScopeNames.AVAILABLE_FOR_LIST_IDS, scopeOptions ] }) |
@@ -1360,7 +1550,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1360 | required: false, | 1550 | required: false, |
1361 | where: { | 1551 | where: { |
1362 | startDate: { | 1552 | startDate: { |
1363 | [ Sequelize.Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) | 1553 | [ Op.gte ]: new Date(new Date().getTime() - (24 * 3600 * 1000) * trendingDays) |
1364 | } | 1554 | } |
1365 | } | 1555 | } |
1366 | } | 1556 | } |
@@ -1377,11 +1567,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1377 | } | 1567 | } |
1378 | 1568 | ||
1379 | private static async getAvailableForApi ( | 1569 | private static async getAvailableForApi ( |
1380 | query: IFindOptions<VideoModel>, | 1570 | query: FindOptions, |
1381 | options: AvailableForListIDsOptions, | 1571 | options: AvailableForListIDsOptions, |
1382 | countVideos = true | 1572 | countVideos = true |
1383 | ) { | 1573 | ) { |
1384 | const idsScope = { | 1574 | const idsScope: ScopeOptions = { |
1385 | method: [ | 1575 | method: [ |
1386 | ScopeNames.AVAILABLE_FOR_LIST_IDS, options | 1576 | ScopeNames.AVAILABLE_FOR_LIST_IDS, options |
1387 | ] | 1577 | ] |
@@ -1389,8 +1579,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1389 | 1579 | ||
1390 | // Remove trending sort on count, because it uses a group by | 1580 | // Remove trending sort on count, because it uses a group by |
1391 | const countOptions = Object.assign({}, options, { trendingDays: undefined }) | 1581 | const countOptions = Object.assign({}, options, { trendingDays: undefined }) |
1392 | const countQuery = Object.assign({}, query, { attributes: undefined, group: undefined }) | 1582 | const countQuery: CountOptions = Object.assign({}, query, { attributes: undefined, group: undefined }) |
1393 | const countScope = { | 1583 | const countScope: ScopeOptions = { |
1394 | method: [ | 1584 | method: [ |
1395 | ScopeNames.AVAILABLE_FOR_LIST_IDS, countOptions | 1585 | ScopeNames.AVAILABLE_FOR_LIST_IDS, countOptions |
1396 | ] | 1586 | ] |
@@ -1404,18 +1594,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1404 | 1594 | ||
1405 | if (ids.length === 0) return { data: [], total: count } | 1595 | if (ids.length === 0) return { data: [], total: count } |
1406 | 1596 | ||
1407 | // FIXME: typings | 1597 | const secondQuery: FindOptions = { |
1408 | const apiScope: any[] = [ | ||
1409 | { | ||
1410 | method: [ ScopeNames.FOR_API, { ids, withFiles: options.withFiles } as ForAPIOptions ] | ||
1411 | } | ||
1412 | ] | ||
1413 | |||
1414 | if (options.user) { | ||
1415 | apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) | ||
1416 | } | ||
1417 | |||
1418 | const secondQuery = { | ||
1419 | offset: 0, | 1598 | offset: 0, |
1420 | limit: query.limit, | 1599 | limit: query.limit, |
1421 | attributes: query.attributes, | 1600 | attributes: query.attributes, |
@@ -1425,6 +1604,23 @@ export class VideoModel extends Model<VideoModel> { | |||
1425 | ) | 1604 | ) |
1426 | ] | 1605 | ] |
1427 | } | 1606 | } |
1607 | |||
1608 | const apiScope: (string | ScopeOptions)[] = [] | ||
1609 | |||
1610 | if (options.user) { | ||
1611 | apiScope.push({ method: [ ScopeNames.WITH_USER_HISTORY, options.user.id ] }) | ||
1612 | } | ||
1613 | |||
1614 | apiScope.push({ | ||
1615 | method: [ | ||
1616 | ScopeNames.FOR_API, { | ||
1617 | ids, | ||
1618 | withFiles: options.withFiles, | ||
1619 | videoPlaylistId: options.videoPlaylistId | ||
1620 | } as ForAPIOptions | ||
1621 | ] | ||
1622 | }) | ||
1623 | |||
1428 | const rows = await VideoModel.scope(apiScope).findAll(secondQuery) | 1624 | const rows = await VideoModel.scope(apiScope).findAll(secondQuery) |
1429 | 1625 | ||
1430 | return { | 1626 | return { |
@@ -1453,10 +1649,6 @@ export class VideoModel extends Model<VideoModel> { | |||
1453 | return VIDEO_STATES[ id ] || 'Unknown' | 1649 | return VIDEO_STATES[ id ] || 'Unknown' |
1454 | } | 1650 | } |
1455 | 1651 | ||
1456 | static buildWhereIdOrUUID (id: number | string) { | ||
1457 | return validator.isInt('' + id) ? { id } : { uuid: id } | ||
1458 | } | ||
1459 | |||
1460 | getOriginalFile () { | 1652 | getOriginalFile () { |
1461 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1653 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1462 | 1654 | ||
@@ -1464,19 +1656,41 @@ export class VideoModel extends Model<VideoModel> { | |||
1464 | return maxBy(this.VideoFiles, file => file.resolution) | 1656 | return maxBy(this.VideoFiles, file => file.resolution) |
1465 | } | 1657 | } |
1466 | 1658 | ||
1659 | async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) { | ||
1660 | thumbnail.videoId = this.id | ||
1661 | |||
1662 | const savedThumbnail = await thumbnail.save({ transaction }) | ||
1663 | |||
1664 | if (Array.isArray(this.Thumbnails) === false) this.Thumbnails = [] | ||
1665 | |||
1666 | // Already have this thumbnail, skip | ||
1667 | if (this.Thumbnails.find(t => t.id === savedThumbnail.id)) return | ||
1668 | |||
1669 | this.Thumbnails.push(savedThumbnail) | ||
1670 | } | ||
1671 | |||
1467 | getVideoFilename (videoFile: VideoFileModel) { | 1672 | getVideoFilename (videoFile: VideoFileModel) { |
1468 | return this.uuid + '-' + videoFile.resolution + videoFile.extname | 1673 | return this.uuid + '-' + videoFile.resolution + videoFile.extname |
1469 | } | 1674 | } |
1470 | 1675 | ||
1471 | getThumbnailName () { | 1676 | generateThumbnailName () { |
1472 | // We always have a copy of the thumbnail | 1677 | return this.uuid + '.jpg' |
1473 | const extension = '.jpg' | ||
1474 | return this.uuid + extension | ||
1475 | } | 1678 | } |
1476 | 1679 | ||
1477 | getPreviewName () { | 1680 | getMiniature () { |
1478 | const extension = '.jpg' | 1681 | if (Array.isArray(this.Thumbnails) === false) return undefined |
1479 | return this.uuid + extension | 1682 | |
1683 | return this.Thumbnails.find(t => t.type === ThumbnailType.MINIATURE) | ||
1684 | } | ||
1685 | |||
1686 | generatePreviewName () { | ||
1687 | return this.uuid + '.jpg' | ||
1688 | } | ||
1689 | |||
1690 | getPreview () { | ||
1691 | if (Array.isArray(this.Thumbnails) === false) return undefined | ||
1692 | |||
1693 | return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW) | ||
1480 | } | 1694 | } |
1481 | 1695 | ||
1482 | getTorrentFileName (videoFile: VideoFileModel) { | 1696 | getTorrentFileName (videoFile: VideoFileModel) { |
@@ -1488,24 +1702,6 @@ export class VideoModel extends Model<VideoModel> { | |||
1488 | return this.remote === false | 1702 | return this.remote === false |
1489 | } | 1703 | } |
1490 | 1704 | ||
1491 | createPreview (videoFile: VideoFileModel) { | ||
1492 | return generateImageFromVideoFile( | ||
1493 | this.getVideoFilePath(videoFile), | ||
1494 | CONFIG.STORAGE.PREVIEWS_DIR, | ||
1495 | this.getPreviewName(), | ||
1496 | PREVIEWS_SIZE | ||
1497 | ) | ||
1498 | } | ||
1499 | |||
1500 | createThumbnail (videoFile: VideoFileModel) { | ||
1501 | return generateImageFromVideoFile( | ||
1502 | this.getVideoFilePath(videoFile), | ||
1503 | CONFIG.STORAGE.THUMBNAILS_DIR, | ||
1504 | this.getThumbnailName(), | ||
1505 | THUMBNAILS_SIZE | ||
1506 | ) | ||
1507 | } | ||
1508 | |||
1509 | getTorrentFilePath (videoFile: VideoFileModel) { | 1705 | getTorrentFilePath (videoFile: VideoFileModel) { |
1510 | return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | 1706 | return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) |
1511 | } | 1707 | } |
@@ -1520,10 +1716,10 @@ export class VideoModel extends Model<VideoModel> { | |||
1520 | name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`, | 1716 | name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`, |
1521 | createdBy: 'PeerTube', | 1717 | createdBy: 'PeerTube', |
1522 | announceList: [ | 1718 | announceList: [ |
1523 | [ CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT + '/tracker/socket' ], | 1719 | [ WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT + '/tracker/socket' ], |
1524 | [ CONFIG.WEBSERVER.URL + '/tracker/announce' ] | 1720 | [ WEBSERVER.URL + '/tracker/announce' ] |
1525 | ], | 1721 | ], |
1526 | urlList: [ CONFIG.WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) ] | 1722 | urlList: [ WEBSERVER.URL + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) ] |
1527 | } | 1723 | } |
1528 | 1724 | ||
1529 | const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options) | 1725 | const torrent = await createTorrentPromise(this.getVideoFilePath(videoFile), options) |
@@ -1545,12 +1741,19 @@ export class VideoModel extends Model<VideoModel> { | |||
1545 | return '/videos/embed/' + this.uuid | 1741 | return '/videos/embed/' + this.uuid |
1546 | } | 1742 | } |
1547 | 1743 | ||
1548 | getThumbnailStaticPath () { | 1744 | getMiniatureStaticPath () { |
1549 | return join(STATIC_PATHS.THUMBNAILS, this.getThumbnailName()) | 1745 | const thumbnail = this.getMiniature() |
1746 | if (!thumbnail) return null | ||
1747 | |||
1748 | return join(STATIC_PATHS.THUMBNAILS, thumbnail.filename) | ||
1550 | } | 1749 | } |
1551 | 1750 | ||
1552 | getPreviewStaticPath () { | 1751 | getPreviewStaticPath () { |
1553 | return join(STATIC_PATHS.PREVIEWS, this.getPreviewName()) | 1752 | const preview = this.getPreview() |
1753 | if (!preview) return null | ||
1754 | |||
1755 | // We use a local cache, so specify our cache endpoint instead of potential remote URL | ||
1756 | return join(STATIC_PATHS.PREVIEWS, preview.filename) | ||
1554 | } | 1757 | } |
1555 | 1758 | ||
1556 | toFormattedJSON (options?: VideoFormattingJSONOptions): Video { | 1759 | toFormattedJSON (options?: VideoFormattingJSONOptions): Video { |
@@ -1586,18 +1789,6 @@ export class VideoModel extends Model<VideoModel> { | |||
1586 | return `/api/${API_VERSION}/videos/${this.uuid}/description` | 1789 | return `/api/${API_VERSION}/videos/${this.uuid}/description` |
1587 | } | 1790 | } |
1588 | 1791 | ||
1589 | removeThumbnail () { | ||
1590 | const thumbnailPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) | ||
1591 | return remove(thumbnailPath) | ||
1592 | .catch(err => logger.warn('Cannot delete thumbnail %s.', thumbnailPath, { err })) | ||
1593 | } | ||
1594 | |||
1595 | removePreview () { | ||
1596 | const previewPath = join(CONFIG.STORAGE.PREVIEWS_DIR + this.getPreviewName()) | ||
1597 | return remove(previewPath) | ||
1598 | .catch(err => logger.warn('Cannot delete preview %s.', previewPath, { err })) | ||
1599 | } | ||
1600 | |||
1601 | removeFile (videoFile: VideoFileModel, isRedundancy = false) { | 1792 | removeFile (videoFile: VideoFileModel, isRedundancy = false) { |
1602 | const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR | 1793 | const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR |
1603 | 1794 | ||
@@ -1612,15 +1803,18 @@ export class VideoModel extends Model<VideoModel> { | |||
1612 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | 1803 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) |
1613 | } | 1804 | } |
1614 | 1805 | ||
1806 | removeStreamingPlaylist (isRedundancy = false) { | ||
1807 | const baseDir = isRedundancy ? HLS_REDUNDANCY_DIRECTORY : HLS_STREAMING_PLAYLIST_DIRECTORY | ||
1808 | |||
1809 | const filePath = join(baseDir, this.uuid) | ||
1810 | return remove(filePath) | ||
1811 | .catch(err => logger.warn('Cannot delete playlist directory %s.', filePath, { err })) | ||
1812 | } | ||
1813 | |||
1615 | isOutdated () { | 1814 | isOutdated () { |
1616 | if (this.isOwned()) return false | 1815 | if (this.isOwned()) return false |
1617 | 1816 | ||
1618 | const now = Date.now() | 1817 | return isOutdated(this, ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL) |
1619 | const createdAtTime = this.createdAt.getTime() | ||
1620 | const updatedAtTime = this.updatedAt.getTime() | ||
1621 | |||
1622 | return (now - createdAtTime) > ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL && | ||
1623 | (now - updatedAtTime) > ACTIVITY_PUB.VIDEO_REFRESH_INTERVAL | ||
1624 | } | 1818 | } |
1625 | 1819 | ||
1626 | setAsRefreshed () { | 1820 | setAsRefreshed () { |
@@ -1634,8 +1828,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1634 | let baseUrlWs | 1828 | let baseUrlWs |
1635 | 1829 | ||
1636 | if (this.isOwned()) { | 1830 | if (this.isOwned()) { |
1637 | baseUrlHttp = CONFIG.WEBSERVER.URL | 1831 | baseUrlHttp = WEBSERVER.URL |
1638 | baseUrlWs = CONFIG.WEBSERVER.WS + '://' + CONFIG.WEBSERVER.HOSTNAME + ':' + CONFIG.WEBSERVER.PORT | 1832 | baseUrlWs = WEBSERVER.WS + '://' + WEBSERVER.HOSTNAME + ':' + WEBSERVER.PORT |
1639 | } else { | 1833 | } else { |
1640 | baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Actor.Server.host | 1834 | baseUrlHttp = REMOTE_SCHEME.HTTP + '://' + this.VideoChannel.Account.Actor.Server.host |
1641 | baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Actor.Server.host | 1835 | baseUrlWs = REMOTE_SCHEME.WS + '://' + this.VideoChannel.Account.Actor.Server.host |
@@ -1646,7 +1840,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1646 | 1840 | ||
1647 | generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { | 1841 | generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { |
1648 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) | 1842 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) |
1649 | const announce = [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | 1843 | const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs) |
1650 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] | 1844 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] |
1651 | 1845 | ||
1652 | const redundancies = videoFile.RedundancyVideos | 1846 | const redundancies = videoFile.RedundancyVideos |
@@ -1663,8 +1857,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1663 | return magnetUtil.encode(magnetHash) | 1857 | return magnetUtil.encode(magnetHash) |
1664 | } | 1858 | } |
1665 | 1859 | ||
1666 | getThumbnailUrl (baseUrlHttp: string) { | 1860 | getTrackerUrls (baseUrlHttp: string, baseUrlWs: string) { |
1667 | return baseUrlHttp + STATIC_PATHS.THUMBNAILS + this.getThumbnailName() | 1861 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] |
1668 | } | 1862 | } |
1669 | 1863 | ||
1670 | getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 1864 | getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { |
@@ -1686,4 +1880,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1686 | getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 1880 | getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { |
1687 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) | 1881 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) |
1688 | } | 1882 | } |
1883 | |||
1884 | getBandwidthBits (videoFile: VideoFileModel) { | ||
1885 | return Math.ceil((videoFile.size * 8) / this.duration) | ||
1886 | } | ||
1689 | } | 1887 | } |
diff --git a/server/tests/api/activitypub/client.ts b/server/tests/api/activitypub/client.ts index 6d90d8643..edf588c16 100644 --- a/server/tests/api/activitypub/client.ts +++ b/server/tests/api/activitypub/client.ts | |||
@@ -11,7 +11,7 @@ import { | |||
11 | ServerInfo, | 11 | ServerInfo, |
12 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
13 | uploadVideo | 13 | uploadVideo |
14 | } from '../../../../shared/utils' | 14 | } from '../../../../shared/extra-utils' |
15 | 15 | ||
16 | const expect = chai.expect | 16 | const expect = chai.expect |
17 | 17 | ||
@@ -22,8 +22,6 @@ describe('Test activitypub', function () { | |||
22 | before(async function () { | 22 | before(async function () { |
23 | this.timeout(30000) | 23 | this.timeout(30000) |
24 | 24 | ||
25 | await flushTests() | ||
26 | |||
27 | servers = await flushAndRunMultipleServers(2) | 25 | servers = await flushAndRunMultipleServers(2) |
28 | 26 | ||
29 | await setAccessTokensToServers(servers) | 27 | await setAccessTokensToServers(servers) |
@@ -61,7 +59,7 @@ describe('Test activitypub', function () { | |||
61 | expect(res.header.location).to.equal('http://localhost:9001/videos/watch/' + videoUUID) | 59 | expect(res.header.location).to.equal('http://localhost:9001/videos/watch/' + videoUUID) |
62 | }) | 60 | }) |
63 | 61 | ||
64 | after(async function () { | 62 | after(function () { |
65 | killallServers(servers) | 63 | killallServers(servers) |
66 | }) | 64 | }) |
67 | }) | 65 | }) |
diff --git a/server/tests/api/activitypub/fetch.ts b/server/tests/api/activitypub/fetch.ts index 03609c1a9..7240bb0fb 100644 --- a/server/tests/api/activitypub/fetch.ts +++ b/server/tests/api/activitypub/fetch.ts | |||
@@ -3,6 +3,7 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | closeAllSequelize, | ||
6 | createUser, | 7 | createUser, |
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
@@ -16,7 +17,7 @@ import { | |||
16 | uploadVideo, | 17 | uploadVideo, |
17 | userLogin, | 18 | userLogin, |
18 | waitJobs | 19 | waitJobs |
19 | } from '../../../../shared/utils' | 20 | } from '../../../../shared/extra-utils' |
20 | import * as chai from 'chai' | 21 | import * as chai from 'chai' |
21 | import { Video } from '../../../../shared/models/videos' | 22 | import { Video } from '../../../../shared/models/videos' |
22 | 23 | ||
@@ -37,7 +38,7 @@ describe('Test ActivityPub fetcher', function () { | |||
37 | 38 | ||
38 | const user = { username: 'user1', password: 'password' } | 39 | const user = { username: 'user1', password: 'password' } |
39 | for (const server of servers) { | 40 | for (const server of servers) { |
40 | await createUser(server.url, server.accessToken, user.username, user.password) | 41 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
41 | } | 42 | } |
42 | 43 | ||
43 | const userAccessToken = await userLogin(servers[0], user) | 44 | const userAccessToken = await userLogin(servers[0], user) |
@@ -79,9 +80,6 @@ describe('Test ActivityPub fetcher', function () { | |||
79 | after(async function () { | 80 | after(async function () { |
80 | killallServers(servers) | 81 | killallServers(servers) |
81 | 82 | ||
82 | // Keep the logs if the test failed | 83 | await closeAllSequelize(servers) |
83 | if (this['ok']) { | ||
84 | await flushTests() | ||
85 | } | ||
86 | }) | 84 | }) |
87 | }) | 85 | }) |
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts index ac6e755c3..365d0e1ae 100644 --- a/server/tests/api/activitypub/helpers.ts +++ b/server/tests/api/activitypub/helpers.ts | |||
@@ -2,7 +2,7 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { expect } from 'chai' | 4 | import { expect } from 'chai' |
5 | import { buildRequestStub } from '../../../../shared/utils/miscs/stubs' | 5 | import { buildRequestStub } from '../../../../shared/extra-utils/miscs/stubs' |
6 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto' | 6 | import { isHTTPSignatureVerified, isJsonLDSignatureVerified, parseHTTPSignature } from '../../../helpers/peertube-crypto' |
7 | import { cloneDeep } from 'lodash' | 7 | import { cloneDeep } from 'lodash' |
8 | import { buildSignedActivity } from '../../../helpers/activitypub' | 8 | import { buildSignedActivity } from '../../../helpers/activitypub' |
diff --git a/server/tests/api/activitypub/index.ts b/server/tests/api/activitypub/index.ts index 450053309..92bd6f660 100644 --- a/server/tests/api/activitypub/index.ts +++ b/server/tests/api/activitypub/index.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import './client' | 1 | import './client' |
2 | import './fetch' | 2 | import './fetch' |
3 | import './helpers' | ||
4 | import './refresher' | 3 | import './refresher' |
4 | import './helpers' | ||
5 | import './security' | 5 | import './security' |
diff --git a/server/tests/api/activitypub/refresher.ts b/server/tests/api/activitypub/refresher.ts index 62ad8a0b5..9be9aa495 100644 --- a/server/tests/api/activitypub/refresher.ts +++ b/server/tests/api/activitypub/refresher.ts | |||
@@ -2,92 +2,158 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { | 4 | import { |
5 | createVideoPlaylist, | ||
5 | doubleFollow, | 6 | doubleFollow, |
6 | flushAndRunMultipleServers, | 7 | flushAndRunMultipleServers, |
8 | generateUserAccessToken, | ||
7 | getVideo, | 9 | getVideo, |
8 | killallServers, | 10 | getVideoPlaylist, |
11 | killallServers, rateVideo, | ||
9 | reRunServer, | 12 | reRunServer, |
10 | ServerInfo, | 13 | ServerInfo, |
11 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
15 | setActorField, | ||
16 | setDefaultVideoChannel, | ||
17 | setPlaylistField, | ||
18 | setVideoField, | ||
12 | uploadVideo, | 19 | uploadVideo, |
20 | uploadVideoAndGetId, | ||
13 | wait, | 21 | wait, |
14 | setVideoField, | ||
15 | waitJobs | 22 | waitJobs |
16 | } from '../../../../shared/utils' | 23 | } from '../../../../shared/extra-utils' |
24 | import { getAccount } from '../../../../shared/extra-utils/users/accounts' | ||
25 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos' | ||
17 | 26 | ||
18 | describe('Test AP refresher', function () { | 27 | describe('Test AP refresher', function () { |
19 | let servers: ServerInfo[] = [] | 28 | let servers: ServerInfo[] = [] |
20 | let videoUUID1: string | 29 | let videoUUID1: string |
21 | let videoUUID2: string | 30 | let videoUUID2: string |
22 | let videoUUID3: string | 31 | let videoUUID3: string |
32 | let playlistUUID1: string | ||
33 | let playlistUUID2: string | ||
23 | 34 | ||
24 | before(async function () { | 35 | before(async function () { |
25 | this.timeout(60000) | 36 | this.timeout(60000) |
26 | 37 | ||
27 | servers = await flushAndRunMultipleServers(2) | 38 | servers = await flushAndRunMultipleServers(2, { transcoding: { enabled: false } }) |
28 | 39 | ||
29 | // Get the access tokens | 40 | // Get the access tokens |
30 | await setAccessTokensToServers(servers) | 41 | await setAccessTokensToServers(servers) |
42 | await setDefaultVideoChannel(servers) | ||
43 | |||
44 | { | ||
45 | videoUUID1 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video1' })).uuid | ||
46 | videoUUID2 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video2' })).uuid | ||
47 | videoUUID3 = (await uploadVideoAndGetId({ server: servers[ 1 ], videoName: 'video3' })).uuid | ||
48 | } | ||
31 | 49 | ||
32 | { | 50 | { |
33 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video1' }) | 51 | const a1 = await generateUserAccessToken(servers[1], 'user1') |
34 | videoUUID1 = res.body.video.uuid | 52 | await uploadVideo(servers[1].url, a1, { name: 'video4' }) |
53 | |||
54 | const a2 = await generateUserAccessToken(servers[1], 'user2') | ||
55 | await uploadVideo(servers[1].url, a2, { name: 'video5' }) | ||
35 | } | 56 | } |
36 | 57 | ||
37 | { | 58 | { |
38 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video2' }) | 59 | const playlistAttrs = { displayName: 'playlist1', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id } |
39 | videoUUID2 = res.body.video.uuid | 60 | const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs }) |
61 | playlistUUID1 = res.body.videoPlaylist.uuid | ||
40 | } | 62 | } |
41 | 63 | ||
42 | { | 64 | { |
43 | const res = await uploadVideo(servers[1].url, servers[1].accessToken, { name: 'video3' }) | 65 | const playlistAttrs = { displayName: 'playlist2', privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: servers[1].videoChannel.id } |
44 | videoUUID3 = res.body.video.uuid | 66 | const res = await createVideoPlaylist({ url: servers[1].url, token: servers[1].accessToken, playlistAttrs }) |
67 | playlistUUID2 = res.body.videoPlaylist.uuid | ||
45 | } | 68 | } |
46 | 69 | ||
47 | await doubleFollow(servers[0], servers[1]) | 70 | await doubleFollow(servers[0], servers[1]) |
48 | }) | 71 | }) |
49 | 72 | ||
50 | it('Should remove a deleted remote video', async function () { | 73 | describe('Videos refresher', function () { |
51 | this.timeout(60000) | 74 | |
75 | it('Should remove a deleted remote video', async function () { | ||
76 | this.timeout(60000) | ||
77 | |||
78 | await wait(10000) | ||
79 | |||
80 | // Change UUID so the remote server returns a 404 | ||
81 | await setVideoField(2, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f') | ||
82 | |||
83 | await getVideo(servers[ 0 ].url, videoUUID1) | ||
84 | await getVideo(servers[ 0 ].url, videoUUID2) | ||
85 | |||
86 | await waitJobs(servers) | ||
52 | 87 | ||
53 | await wait(10000) | 88 | await getVideo(servers[ 0 ].url, videoUUID1, 404) |
89 | await getVideo(servers[ 0 ].url, videoUUID2, 200) | ||
90 | }) | ||
54 | 91 | ||
55 | // Change UUID so the remote server returns a 404 | 92 | it('Should not update a remote video if the remote instance is down', async function () { |
56 | await setVideoField(2, videoUUID1, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174f') | 93 | this.timeout(60000) |
57 | 94 | ||
58 | await getVideo(servers[0].url, videoUUID1) | 95 | killallServers([ servers[ 1 ] ]) |
59 | await getVideo(servers[0].url, videoUUID2) | ||
60 | 96 | ||
61 | await waitJobs(servers) | 97 | await setVideoField(2, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e') |
62 | 98 | ||
63 | await getVideo(servers[0].url, videoUUID1, 404) | 99 | // Video will need a refresh |
64 | await getVideo(servers[0].url, videoUUID2, 200) | 100 | await wait(10000) |
101 | |||
102 | await getVideo(servers[ 0 ].url, videoUUID3) | ||
103 | // The refresh should fail | ||
104 | await waitJobs([ servers[ 0 ] ]) | ||
105 | |||
106 | await reRunServer(servers[ 1 ]) | ||
107 | |||
108 | // Should not refresh the video, even if the last refresh failed (to avoir a loop on dead instances) | ||
109 | await getVideo(servers[ 0 ].url, videoUUID3) | ||
110 | await waitJobs(servers) | ||
111 | |||
112 | await getVideo(servers[ 0 ].url, videoUUID3, 200) | ||
113 | }) | ||
65 | }) | 114 | }) |
66 | 115 | ||
67 | it('Should not update a remote video if the remote instance is down', async function () { | 116 | describe('Actors refresher', function () { |
68 | this.timeout(60000) | 117 | |
118 | it('Should remove a deleted actor', async function () { | ||
119 | this.timeout(60000) | ||
120 | |||
121 | await wait(10000) | ||
122 | |||
123 | // Change actor name so the remote server returns a 404 | ||
124 | await setActorField(2, 'http://localhost:9002/accounts/user2', 'preferredUsername', 'toto') | ||
125 | |||
126 | await getAccount(servers[ 0 ].url, 'user1@localhost:9002') | ||
127 | await getAccount(servers[ 0 ].url, 'user2@localhost:9002') | ||
128 | |||
129 | await waitJobs(servers) | ||
130 | |||
131 | await getAccount(servers[ 0 ].url, 'user1@localhost:9002', 200) | ||
132 | await getAccount(servers[ 0 ].url, 'user2@localhost:9002', 404) | ||
133 | }) | ||
134 | }) | ||
69 | 135 | ||
70 | killallServers([ servers[1] ]) | 136 | describe('Playlist refresher', function () { |
71 | 137 | ||
72 | await setVideoField(2, videoUUID3, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b174e') | 138 | it('Should remove a deleted playlist', async function () { |
139 | this.timeout(60000) | ||
73 | 140 | ||
74 | // Video will need a refresh | 141 | await wait(10000) |
75 | await wait(10000) | ||
76 | 142 | ||
77 | await getVideo(servers[0].url, videoUUID3) | 143 | // Change UUID so the remote server returns a 404 |
78 | // The refresh should fail | 144 | await setPlaylistField(2, playlistUUID2, 'uuid', '304afe4f-39f9-4d49-8ed7-ac57b86b178e') |
79 | await waitJobs([ servers[0] ]) | ||
80 | 145 | ||
81 | await reRunServer(servers[1]) | 146 | await getVideoPlaylist(servers[ 0 ].url, playlistUUID1) |
147 | await getVideoPlaylist(servers[ 0 ].url, playlistUUID2) | ||
82 | 148 | ||
83 | // Should not refresh the video, even if the last refresh failed (to avoir a loop on dead instances) | 149 | await waitJobs(servers) |
84 | await getVideo(servers[0].url, videoUUID3) | ||
85 | await waitJobs(servers) | ||
86 | 150 | ||
87 | await getVideo(servers[0].url, videoUUID3, 200) | 151 | await getVideoPlaylist(servers[ 0 ].url, playlistUUID1, 200) |
152 | await getVideoPlaylist(servers[ 0 ].url, playlistUUID2, 404) | ||
153 | }) | ||
88 | }) | 154 | }) |
89 | 155 | ||
90 | after(async function () { | 156 | after(function () { |
91 | killallServers(servers) | 157 | killallServers(servers) |
92 | }) | 158 | }) |
93 | }) | 159 | }) |
diff --git a/server/tests/api/activitypub/security.ts b/server/tests/api/activitypub/security.ts index 342ae0fa1..11e6859bf 100644 --- a/server/tests/api/activitypub/security.ts +++ b/server/tests/api/activitypub/security.ts | |||
@@ -3,18 +3,18 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | closeAllSequelize, | ||
6 | flushAndRunMultipleServers, | 7 | flushAndRunMultipleServers, |
7 | flushTests, | 8 | flushTests, |
8 | killallServers, | 9 | killallServers, |
9 | makeFollowRequest, | ||
10 | makePOSTAPRequest, | ||
11 | ServerInfo, | 10 | ServerInfo, |
12 | setActorField | 11 | setActorField |
13 | } from '../../../../shared/utils' | 12 | } from '../../../../shared/extra-utils' |
14 | import { HTTP_SIGNATURE } from '../../../initializers' | 13 | import { HTTP_SIGNATURE } from '../../../initializers/constants' |
15 | import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils' | 14 | import { buildDigest, buildGlobalHeaders } from '../../../lib/job-queue/handlers/utils/activitypub-http-utils' |
16 | import * as chai from 'chai' | 15 | import * as chai from 'chai' |
17 | import { activityPubContextify, buildSignedActivity } from '../../../helpers/activitypub' | 16 | import { activityPubContextify, buildSignedActivity } from '../../../helpers/activitypub' |
17 | import { makeFollowRequest, makePOSTAPRequest } from '../../../../shared/extra-utils/requests/activitypub' | ||
18 | 18 | ||
19 | const expect = chai.expect | 19 | const expect = chai.expect |
20 | 20 | ||
@@ -179,9 +179,6 @@ describe('Test ActivityPub security', function () { | |||
179 | after(async function () { | 179 | after(async function () { |
180 | killallServers(servers) | 180 | killallServers(servers) |
181 | 181 | ||
182 | // Keep the logs if the test failed | 182 | await closeAllSequelize(servers) |
183 | if (this['ok']) { | ||
184 | await flushTests() | ||
185 | } | ||
186 | }) | 183 | }) |
187 | }) | 184 | }) |
diff --git a/server/tests/api/check-params/accounts.ts b/server/tests/api/check-params/accounts.ts index 68f9519c6..4f79685bd 100644 --- a/server/tests/api/check-params/accounts.ts +++ b/server/tests/api/check-params/accounts.ts | |||
@@ -2,13 +2,13 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { flushTests, killallServers, runServer, ServerInfo } from '../../../../shared/utils' | 5 | import { cleanupTests, flushAndRunServer, ServerInfo } from '../../../../shared/extra-utils' |
6 | import { | 6 | import { |
7 | checkBadCountPagination, | 7 | checkBadCountPagination, |
8 | checkBadSortPagination, | 8 | checkBadSortPagination, |
9 | checkBadStartPagination | 9 | checkBadStartPagination |
10 | } from '../../../../shared/utils/requests/check-api-params' | 10 | } from '../../../../shared/extra-utils/requests/check-api-params' |
11 | import { getAccount } from '../../../../shared/utils/users/accounts' | 11 | import { getAccount } from '../../../../shared/extra-utils/users/accounts' |
12 | 12 | ||
13 | describe('Test accounts API validators', function () { | 13 | describe('Test accounts API validators', function () { |
14 | const path = '/api/v1/accounts/' | 14 | const path = '/api/v1/accounts/' |
@@ -19,9 +19,7 @@ describe('Test accounts API validators', function () { | |||
19 | before(async function () { | 19 | before(async function () { |
20 | this.timeout(30000) | 20 | this.timeout(30000) |
21 | 21 | ||
22 | await flushTests() | 22 | server = await flushAndRunServer(1) |
23 | |||
24 | server = await runServer(1) | ||
25 | }) | 23 | }) |
26 | 24 | ||
27 | describe('When listing accounts', function () { | 25 | describe('When listing accounts', function () { |
@@ -45,11 +43,6 @@ describe('Test accounts API validators', function () { | |||
45 | }) | 43 | }) |
46 | 44 | ||
47 | after(async function () { | 45 | after(async function () { |
48 | killallServers([ server ]) | 46 | await cleanupTests([ server ]) |
49 | |||
50 | // Keep the logs if the test failed | ||
51 | if (this['ok']) { | ||
52 | await flushTests() | ||
53 | } | ||
54 | }) | 47 | }) |
55 | }) | 48 | }) |
diff --git a/server/tests/api/check-params/blocklist.ts b/server/tests/api/check-params/blocklist.ts index c20453c16..0661676ce 100644 --- a/server/tests/api/check-params/blocklist.ts +++ b/server/tests/api/check-params/blocklist.ts | |||
@@ -3,22 +3,22 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
9 | flushTests, | ||
10 | killallServers, | ||
11 | makeDeleteRequest, | 10 | makeDeleteRequest, |
12 | makeGetRequest, | 11 | makeGetRequest, |
13 | makePostBodyRequest, | 12 | makePostBodyRequest, |
14 | ServerInfo, | 13 | ServerInfo, |
15 | setAccessTokensToServers, userLogin | 14 | setAccessTokensToServers, |
16 | } from '../../../../shared/utils' | 15 | userLogin |
16 | } from '../../../../shared/extra-utils' | ||
17 | import { | 17 | import { |
18 | checkBadCountPagination, | 18 | checkBadCountPagination, |
19 | checkBadSortPagination, | 19 | checkBadSortPagination, |
20 | checkBadStartPagination | 20 | checkBadStartPagination |
21 | } from '../../../../shared/utils/requests/check-api-params' | 21 | } from '../../../../shared/extra-utils/requests/check-api-params' |
22 | 22 | ||
23 | describe('Test blocklist API validators', function () { | 23 | describe('Test blocklist API validators', function () { |
24 | let servers: ServerInfo[] | 24 | let servers: ServerInfo[] |
@@ -28,15 +28,13 @@ describe('Test blocklist API validators', function () { | |||
28 | before(async function () { | 28 | before(async function () { |
29 | this.timeout(60000) | 29 | this.timeout(60000) |
30 | 30 | ||
31 | await flushTests() | ||
32 | |||
33 | servers = await flushAndRunMultipleServers(2) | 31 | servers = await flushAndRunMultipleServers(2) |
34 | await setAccessTokensToServers(servers) | 32 | await setAccessTokensToServers(servers) |
35 | 33 | ||
36 | server = servers[0] | 34 | server = servers[0] |
37 | 35 | ||
38 | const user = { username: 'user1', password: 'password' } | 36 | const user = { username: 'user1', password: 'password' } |
39 | await createUser(server.url, server.accessToken, user.username, user.password) | 37 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
40 | 38 | ||
41 | userAccessToken = await userLogin(server, user) | 39 | userAccessToken = await userLogin(server, user) |
42 | 40 | ||
@@ -192,7 +190,7 @@ describe('Test blocklist API validators', function () { | |||
192 | url: server.url, | 190 | url: server.url, |
193 | token: server.accessToken, | 191 | token: server.accessToken, |
194 | path, | 192 | path, |
195 | fields: { host: 'localhost:9001' }, | 193 | fields: { host: 'localhost:' + server.port }, |
196 | statusCodeExpected: 409 | 194 | statusCodeExpected: 409 |
197 | }) | 195 | }) |
198 | }) | 196 | }) |
@@ -202,7 +200,7 @@ describe('Test blocklist API validators', function () { | |||
202 | url: server.url, | 200 | url: server.url, |
203 | token: server.accessToken, | 201 | token: server.accessToken, |
204 | path, | 202 | path, |
205 | fields: { host: 'localhost:9002' }, | 203 | fields: { host: 'localhost:' + servers[1].port }, |
206 | statusCodeExpected: 204 | 204 | statusCodeExpected: 204 |
207 | }) | 205 | }) |
208 | }) | 206 | }) |
@@ -212,7 +210,7 @@ describe('Test blocklist API validators', function () { | |||
212 | it('Should fail with an unauthenticated user', async function () { | 210 | it('Should fail with an unauthenticated user', async function () { |
213 | await makeDeleteRequest({ | 211 | await makeDeleteRequest({ |
214 | url: server.url, | 212 | url: server.url, |
215 | path: path + '/localhost:9002', | 213 | path: path + '/localhost:' + servers[1].port, |
216 | statusCodeExpected: 401 | 214 | statusCodeExpected: 401 |
217 | }) | 215 | }) |
218 | }) | 216 | }) |
@@ -229,7 +227,7 @@ describe('Test blocklist API validators', function () { | |||
229 | it('Should succeed with the correct params', async function () { | 227 | it('Should succeed with the correct params', async function () { |
230 | await makeDeleteRequest({ | 228 | await makeDeleteRequest({ |
231 | url: server.url, | 229 | url: server.url, |
232 | path: path + '/localhost:9002', | 230 | path: path + '/localhost:' + servers[1].port, |
233 | token: server.accessToken, | 231 | token: server.accessToken, |
234 | statusCodeExpected: 204 | 232 | statusCodeExpected: 204 |
235 | }) | 233 | }) |
@@ -402,7 +400,7 @@ describe('Test blocklist API validators', function () { | |||
402 | await makePostBodyRequest({ | 400 | await makePostBodyRequest({ |
403 | url: server.url, | 401 | url: server.url, |
404 | path, | 402 | path, |
405 | fields: { host: 'localhost:9002' }, | 403 | fields: { host: 'localhost:' + servers[1].port }, |
406 | statusCodeExpected: 401 | 404 | statusCodeExpected: 401 |
407 | }) | 405 | }) |
408 | }) | 406 | }) |
@@ -412,7 +410,7 @@ describe('Test blocklist API validators', function () { | |||
412 | url: server.url, | 410 | url: server.url, |
413 | token: userAccessToken, | 411 | token: userAccessToken, |
414 | path, | 412 | path, |
415 | fields: { host: 'localhost:9002' }, | 413 | fields: { host: 'localhost:' + servers[1].port }, |
416 | statusCodeExpected: 403 | 414 | statusCodeExpected: 403 |
417 | }) | 415 | }) |
418 | }) | 416 | }) |
@@ -432,7 +430,7 @@ describe('Test blocklist API validators', function () { | |||
432 | url: server.url, | 430 | url: server.url, |
433 | token: server.accessToken, | 431 | token: server.accessToken, |
434 | path, | 432 | path, |
435 | fields: { host: 'localhost:9001' }, | 433 | fields: { host: 'localhost:' + server.port }, |
436 | statusCodeExpected: 409 | 434 | statusCodeExpected: 409 |
437 | }) | 435 | }) |
438 | }) | 436 | }) |
@@ -442,7 +440,7 @@ describe('Test blocklist API validators', function () { | |||
442 | url: server.url, | 440 | url: server.url, |
443 | token: server.accessToken, | 441 | token: server.accessToken, |
444 | path, | 442 | path, |
445 | fields: { host: 'localhost:9002' }, | 443 | fields: { host: 'localhost:' + servers[1].port }, |
446 | statusCodeExpected: 204 | 444 | statusCodeExpected: 204 |
447 | }) | 445 | }) |
448 | }) | 446 | }) |
@@ -452,7 +450,7 @@ describe('Test blocklist API validators', function () { | |||
452 | it('Should fail with an unauthenticated user', async function () { | 450 | it('Should fail with an unauthenticated user', async function () { |
453 | await makeDeleteRequest({ | 451 | await makeDeleteRequest({ |
454 | url: server.url, | 452 | url: server.url, |
455 | path: path + '/localhost:9002', | 453 | path: path + '/localhost:' + servers[1].port, |
456 | statusCodeExpected: 401 | 454 | statusCodeExpected: 401 |
457 | }) | 455 | }) |
458 | }) | 456 | }) |
@@ -460,7 +458,7 @@ describe('Test blocklist API validators', function () { | |||
460 | it('Should fail with a user without the appropriate rights', async function () { | 458 | it('Should fail with a user without the appropriate rights', async function () { |
461 | await makeDeleteRequest({ | 459 | await makeDeleteRequest({ |
462 | url: server.url, | 460 | url: server.url, |
463 | path: path + '/localhost:9002', | 461 | path: path + '/localhost:' + servers[1].port, |
464 | token: userAccessToken, | 462 | token: userAccessToken, |
465 | statusCodeExpected: 403 | 463 | statusCodeExpected: 403 |
466 | }) | 464 | }) |
@@ -478,7 +476,7 @@ describe('Test blocklist API validators', function () { | |||
478 | it('Should succeed with the correct params', async function () { | 476 | it('Should succeed with the correct params', async function () { |
479 | await makeDeleteRequest({ | 477 | await makeDeleteRequest({ |
480 | url: server.url, | 478 | url: server.url, |
481 | path: path + '/localhost:9002', | 479 | path: path + '/localhost:' + servers[1].port, |
482 | token: server.accessToken, | 480 | token: server.accessToken, |
483 | statusCodeExpected: 204 | 481 | statusCodeExpected: 204 |
484 | }) | 482 | }) |
@@ -488,11 +486,6 @@ describe('Test blocklist API validators', function () { | |||
488 | }) | 486 | }) |
489 | 487 | ||
490 | after(async function () { | 488 | after(async function () { |
491 | killallServers(servers) | 489 | await cleanupTests(servers) |
492 | |||
493 | // Keep the logs if the test failed | ||
494 | if (this['ok']) { | ||
495 | await flushTests() | ||
496 | } | ||
497 | }) | 490 | }) |
498 | }) | 491 | }) |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index 4038ecbf0..2a2ec606a 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -5,9 +5,9 @@ import 'mocha' | |||
5 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' |
6 | 6 | ||
7 | import { | 7 | import { |
8 | createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, runServer, ServerInfo, | 8 | createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, flushAndRunServer, ServerInfo, |
9 | setAccessTokensToServers, userLogin, immutableAssign | 9 | setAccessTokensToServers, userLogin, immutableAssign, cleanupTests |
10 | } from '../../../../shared/utils' | 10 | } from '../../../../shared/extra-utils' |
11 | 11 | ||
12 | describe('Test config API validators', function () { | 12 | describe('Test config API validators', function () { |
13 | const path = '/api/v1/config/custom' | 13 | const path = '/api/v1/config/custom' |
@@ -19,6 +19,7 @@ describe('Test config API validators', function () { | |||
19 | shortDescription: 'my short description', | 19 | shortDescription: 'my short description', |
20 | description: 'my super description', | 20 | description: 'my super description', |
21 | terms: 'my super terms', | 21 | terms: 'my super terms', |
22 | isNSFW: true, | ||
22 | defaultClientRoute: '/videos/recently-added', | 23 | defaultClientRoute: '/videos/recently-added', |
23 | defaultNSFWPolicy: 'blur', | 24 | defaultNSFWPolicy: 'blur', |
24 | customizations: { | 25 | customizations: { |
@@ -65,6 +66,9 @@ describe('Test config API validators', function () { | |||
65 | '480p': true, | 66 | '480p': true, |
66 | '720p': false, | 67 | '720p': false, |
67 | '1080p': false | 68 | '1080p': false |
69 | }, | ||
70 | hls: { | ||
71 | enabled: false | ||
68 | } | 72 | } |
69 | }, | 73 | }, |
70 | import: { | 74 | import: { |
@@ -76,6 +80,19 @@ describe('Test config API validators', function () { | |||
76 | enabled: false | 80 | enabled: false |
77 | } | 81 | } |
78 | } | 82 | } |
83 | }, | ||
84 | autoBlacklist: { | ||
85 | videos: { | ||
86 | ofUsers: { | ||
87 | enabled: false | ||
88 | } | ||
89 | } | ||
90 | }, | ||
91 | followers: { | ||
92 | instance: { | ||
93 | enabled: false, | ||
94 | manualApproval: true | ||
95 | } | ||
79 | } | 96 | } |
80 | } | 97 | } |
81 | 98 | ||
@@ -84,8 +101,7 @@ describe('Test config API validators', function () { | |||
84 | before(async function () { | 101 | before(async function () { |
85 | this.timeout(30000) | 102 | this.timeout(30000) |
86 | 103 | ||
87 | await flushTests() | 104 | server = await flushAndRunServer(1) |
88 | server = await runServer(1) | ||
89 | 105 | ||
90 | await setAccessTokensToServers([ server ]) | 106 | await setAccessTokensToServers([ server ]) |
91 | 107 | ||
@@ -93,7 +109,7 @@ describe('Test config API validators', function () { | |||
93 | username: 'user1', | 109 | username: 'user1', |
94 | password: 'password' | 110 | password: 'password' |
95 | } | 111 | } |
96 | await createUser(server.url, server.accessToken, user.username, user.password) | 112 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
97 | userAccessToken = await userLogin(server, user) | 113 | userAccessToken = await userLogin(server, user) |
98 | }) | 114 | }) |
99 | 115 | ||
@@ -164,6 +180,25 @@ describe('Test config API validators', function () { | |||
164 | }) | 180 | }) |
165 | }) | 181 | }) |
166 | 182 | ||
183 | it('Should fail if email disabled and signup requires email verification', async function () { | ||
184 | // opposite scenario - success when enable enabled - covered via tests/api/users/user-verification.ts | ||
185 | const newUpdateParams = immutableAssign(updateParams, { | ||
186 | signup: { | ||
187 | enabled: true, | ||
188 | limit: 5, | ||
189 | requiresEmailVerification: true | ||
190 | } | ||
191 | }) | ||
192 | |||
193 | await makePutBodyRequest({ | ||
194 | url: server.url, | ||
195 | path, | ||
196 | fields: newUpdateParams, | ||
197 | token: server.accessToken, | ||
198 | statusCodeExpected: 400 | ||
199 | }) | ||
200 | }) | ||
201 | |||
167 | it('Should success with the correct parameters', async function () { | 202 | it('Should success with the correct parameters', async function () { |
168 | await makePutBodyRequest({ | 203 | await makePutBodyRequest({ |
169 | url: server.url, | 204 | url: server.url, |
@@ -195,11 +230,6 @@ describe('Test config API validators', function () { | |||
195 | }) | 230 | }) |
196 | 231 | ||
197 | after(async function () { | 232 | after(async function () { |
198 | killallServers([ server ]) | 233 | await cleanupTests([ server ]) |
199 | |||
200 | // Keep the logs if the test failed | ||
201 | if (this['ok']) { | ||
202 | await flushTests() | ||
203 | } | ||
204 | }) | 234 | }) |
205 | }) | 235 | }) |
diff --git a/server/tests/api/check-params/contact-form.ts b/server/tests/api/check-params/contact-form.ts index c7e014b1f..dbdd3a8a6 100644 --- a/server/tests/api/check-params/contact-form.ts +++ b/server/tests/api/check-params/contact-form.ts | |||
@@ -7,18 +7,18 @@ import { | |||
7 | immutableAssign, | 7 | immutableAssign, |
8 | killallServers, | 8 | killallServers, |
9 | reRunServer, | 9 | reRunServer, |
10 | runServer, | 10 | flushAndRunServer, |
11 | ServerInfo, | 11 | ServerInfo, |
12 | setAccessTokensToServers | 12 | setAccessTokensToServers, cleanupTests |
13 | } from '../../../../shared/utils' | 13 | } from '../../../../shared/extra-utils' |
14 | import { | 14 | import { |
15 | checkBadCountPagination, | 15 | checkBadCountPagination, |
16 | checkBadSortPagination, | 16 | checkBadSortPagination, |
17 | checkBadStartPagination | 17 | checkBadStartPagination |
18 | } from '../../../../shared/utils/requests/check-api-params' | 18 | } from '../../../../shared/extra-utils/requests/check-api-params' |
19 | import { getAccount } from '../../../../shared/utils/users/accounts' | 19 | import { getAccount } from '../../../../shared/extra-utils/users/accounts' |
20 | import { sendContactForm } from '../../../../shared/utils/server/contact-form' | 20 | import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' |
21 | import { MockSmtpServer } from '../../../../shared/utils/miscs/email' | 21 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' |
22 | 22 | ||
23 | describe('Test contact form API validators', function () { | 23 | describe('Test contact form API validators', function () { |
24 | let server: ServerInfo | 24 | let server: ServerInfo |
@@ -28,17 +28,17 @@ describe('Test contact form API validators', function () { | |||
28 | fromEmail: 'toto@example.com', | 28 | fromEmail: 'toto@example.com', |
29 | body: 'Hello, how are you?' | 29 | body: 'Hello, how are you?' |
30 | } | 30 | } |
31 | let emailPort: number | ||
31 | 32 | ||
32 | // --------------------------------------------------------------- | 33 | // --------------------------------------------------------------- |
33 | 34 | ||
34 | before(async function () { | 35 | before(async function () { |
35 | this.timeout(60000) | 36 | this.timeout(60000) |
36 | 37 | ||
37 | await flushTests() | 38 | emailPort = await MockSmtpServer.Instance.collectEmails(emails) |
38 | await MockSmtpServer.Instance.collectEmails(emails) | ||
39 | 39 | ||
40 | // Email is disabled | 40 | // Email is disabled |
41 | server = await runServer(1) | 41 | server = await flushAndRunServer(1) |
42 | }) | 42 | }) |
43 | 43 | ||
44 | it('Should not accept a contact form if emails are disabled', async function () { | 44 | it('Should not accept a contact form if emails are disabled', async function () { |
@@ -51,7 +51,7 @@ describe('Test contact form API validators', function () { | |||
51 | killallServers([ server ]) | 51 | killallServers([ server ]) |
52 | 52 | ||
53 | // Contact form is disabled | 53 | // Contact form is disabled |
54 | await reRunServer(server, { smtp: { hostname: 'localhost' }, contact_form: { enabled: false } }) | 54 | await reRunServer(server, { smtp: { hostname: 'localhost', port: emailPort }, contact_form: { enabled: false } }) |
55 | await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 409 })) | 55 | await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 409 })) |
56 | }) | 56 | }) |
57 | 57 | ||
@@ -61,7 +61,7 @@ describe('Test contact form API validators', function () { | |||
61 | killallServers([ server ]) | 61 | killallServers([ server ]) |
62 | 62 | ||
63 | // Email & contact form enabled | 63 | // Email & contact form enabled |
64 | await reRunServer(server, { smtp: { hostname: 'localhost' } }) | 64 | await reRunServer(server, { smtp: { hostname: 'localhost', port: emailPort } }) |
65 | 65 | ||
66 | await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromEmail: 'badEmail' })) | 66 | await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromEmail: 'badEmail' })) |
67 | await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromEmail: 'badEmail@' })) | 67 | await sendContactForm(immutableAssign(defaultBody, { url: server.url, expectedStatus: 400, fromEmail: 'badEmail@' })) |
@@ -86,11 +86,7 @@ describe('Test contact form API validators', function () { | |||
86 | 86 | ||
87 | after(async function () { | 87 | after(async function () { |
88 | MockSmtpServer.Instance.kill() | 88 | MockSmtpServer.Instance.kill() |
89 | killallServers([ server ]) | ||
90 | 89 | ||
91 | // Keep the logs if the test failed | 90 | await cleanupTests([ server ]) |
92 | if (this['ok']) { | ||
93 | await flushTests() | ||
94 | } | ||
95 | }) | 91 | }) |
96 | }) | 92 | }) |
diff --git a/server/tests/api/check-params/debug.ts b/server/tests/api/check-params/debug.ts new file mode 100644 index 000000000..8dad26723 --- /dev/null +++ b/server/tests/api/check-params/debug.ts | |||
@@ -0,0 +1,71 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import 'mocha' | ||
4 | |||
5 | import { | ||
6 | createUser, | ||
7 | flushTests, | ||
8 | killallServers, | ||
9 | flushAndRunServer, | ||
10 | ServerInfo, | ||
11 | setAccessTokensToServers, | ||
12 | userLogin, cleanupTests | ||
13 | } from '../../../../shared/extra-utils' | ||
14 | import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' | ||
15 | |||
16 | describe('Test debug API validators', function () { | ||
17 | const path = '/api/v1/server/debug' | ||
18 | let server: ServerInfo | ||
19 | let userAccessToken = '' | ||
20 | |||
21 | // --------------------------------------------------------------- | ||
22 | |||
23 | before(async function () { | ||
24 | this.timeout(120000) | ||
25 | |||
26 | server = await flushAndRunServer(1) | ||
27 | |||
28 | await setAccessTokensToServers([ server ]) | ||
29 | |||
30 | const user = { | ||
31 | username: 'user1', | ||
32 | password: 'my super password' | ||
33 | } | ||
34 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) | ||
35 | userAccessToken = await userLogin(server, user) | ||
36 | }) | ||
37 | |||
38 | describe('When getting debug endpoint', function () { | ||
39 | |||
40 | it('Should fail with a non authenticated user', async function () { | ||
41 | await makeGetRequest({ | ||
42 | url: server.url, | ||
43 | path, | ||
44 | statusCodeExpected: 401 | ||
45 | }) | ||
46 | }) | ||
47 | |||
48 | it('Should fail with a non admin user', async function () { | ||
49 | await makeGetRequest({ | ||
50 | url: server.url, | ||
51 | path, | ||
52 | token: userAccessToken, | ||
53 | statusCodeExpected: 403 | ||
54 | }) | ||
55 | }) | ||
56 | |||
57 | it('Should succeed with the correct params', async function () { | ||
58 | await makeGetRequest({ | ||
59 | url: server.url, | ||
60 | path, | ||
61 | token: server.accessToken, | ||
62 | query: { startDate: new Date().toISOString() }, | ||
63 | statusCodeExpected: 200 | ||
64 | }) | ||
65 | }) | ||
66 | }) | ||
67 | |||
68 | after(async function () { | ||
69 | await cleanupTests([ server ]) | ||
70 | }) | ||
71 | }) | ||
diff --git a/server/tests/api/check-params/follows.ts b/server/tests/api/check-params/follows.ts index 2ad1575a3..2eb54cb0a 100644 --- a/server/tests/api/check-params/follows.ts +++ b/server/tests/api/check-params/follows.ts | |||
@@ -3,14 +3,20 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | createUser, flushTests, killallServers, makeDeleteRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers, | 6 | cleanupTests, |
7 | createUser, | ||
8 | flushAndRunServer, | ||
9 | makeDeleteRequest, | ||
10 | makePostBodyRequest, | ||
11 | ServerInfo, | ||
12 | setAccessTokensToServers, | ||
7 | userLogin | 13 | userLogin |
8 | } from '../../../../shared/utils' | 14 | } from '../../../../shared/extra-utils' |
9 | import { | 15 | import { |
10 | checkBadCountPagination, | 16 | checkBadCountPagination, |
11 | checkBadSortPagination, | 17 | checkBadSortPagination, |
12 | checkBadStartPagination | 18 | checkBadStartPagination |
13 | } from '../../../../shared/utils/requests/check-api-params' | 19 | } from '../../../../shared/extra-utils/requests/check-api-params' |
14 | 20 | ||
15 | describe('Test server follows API validators', function () { | 21 | describe('Test server follows API validators', function () { |
16 | let server: ServerInfo | 22 | let server: ServerInfo |
@@ -20,8 +26,7 @@ describe('Test server follows API validators', function () { | |||
20 | before(async function () { | 26 | before(async function () { |
21 | this.timeout(30000) | 27 | this.timeout(30000) |
22 | 28 | ||
23 | await flushTests() | 29 | server = await flushAndRunServer(1) |
24 | server = await runServer(1) | ||
25 | 30 | ||
26 | await setAccessTokensToServers([ server ]) | 31 | await setAccessTokensToServers([ server ]) |
27 | }) | 32 | }) |
@@ -35,7 +40,7 @@ describe('Test server follows API validators', function () { | |||
35 | password: 'password' | 40 | password: 'password' |
36 | } | 41 | } |
37 | 42 | ||
38 | await createUser(server.url, server.accessToken, user.username, user.password) | 43 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
39 | userAccessToken = await userLogin(server, user) | 44 | userAccessToken = await userLogin(server, user) |
40 | }) | 45 | }) |
41 | 46 | ||
@@ -144,6 +149,126 @@ describe('Test server follows API validators', function () { | |||
144 | }) | 149 | }) |
145 | }) | 150 | }) |
146 | 151 | ||
152 | describe('When removing a follower', function () { | ||
153 | const path = '/api/v1/server/followers' | ||
154 | |||
155 | it('Should fail with an invalid token', async function () { | ||
156 | await makeDeleteRequest({ | ||
157 | url: server.url, | ||
158 | path: path + '/toto@localhost:9002', | ||
159 | token: 'fake_token', | ||
160 | statusCodeExpected: 401 | ||
161 | }) | ||
162 | }) | ||
163 | |||
164 | it('Should fail if the user is not an administrator', async function () { | ||
165 | await makeDeleteRequest({ | ||
166 | url: server.url, | ||
167 | path: path + '/toto@localhost:9002', | ||
168 | token: userAccessToken, | ||
169 | statusCodeExpected: 403 | ||
170 | }) | ||
171 | }) | ||
172 | |||
173 | it('Should fail with an invalid follower', async function () { | ||
174 | await makeDeleteRequest({ | ||
175 | url: server.url, | ||
176 | path: path + '/toto', | ||
177 | token: server.accessToken, | ||
178 | statusCodeExpected: 400 | ||
179 | }) | ||
180 | }) | ||
181 | |||
182 | it('Should fail with an unknown follower', async function () { | ||
183 | await makeDeleteRequest({ | ||
184 | url: server.url, | ||
185 | path: path + '/toto@localhost:9003', | ||
186 | token: server.accessToken, | ||
187 | statusCodeExpected: 404 | ||
188 | }) | ||
189 | }) | ||
190 | }) | ||
191 | |||
192 | describe('When accepting a follower', function () { | ||
193 | const path = '/api/v1/server/followers' | ||
194 | |||
195 | it('Should fail with an invalid token', async function () { | ||
196 | await makePostBodyRequest({ | ||
197 | url: server.url, | ||
198 | path: path + '/toto@localhost:9002/accept', | ||
199 | token: 'fake_token', | ||
200 | statusCodeExpected: 401 | ||
201 | }) | ||
202 | }) | ||
203 | |||
204 | it('Should fail if the user is not an administrator', async function () { | ||
205 | await makePostBodyRequest({ | ||
206 | url: server.url, | ||
207 | path: path + '/toto@localhost:9002/accept', | ||
208 | token: userAccessToken, | ||
209 | statusCodeExpected: 403 | ||
210 | }) | ||
211 | }) | ||
212 | |||
213 | it('Should fail with an invalid follower', async function () { | ||
214 | await makePostBodyRequest({ | ||
215 | url: server.url, | ||
216 | path: path + '/toto/accept', | ||
217 | token: server.accessToken, | ||
218 | statusCodeExpected: 400 | ||
219 | }) | ||
220 | }) | ||
221 | |||
222 | it('Should fail with an unknown follower', async function () { | ||
223 | await makePostBodyRequest({ | ||
224 | url: server.url, | ||
225 | path: path + '/toto@localhost:9003/accept', | ||
226 | token: server.accessToken, | ||
227 | statusCodeExpected: 404 | ||
228 | }) | ||
229 | }) | ||
230 | }) | ||
231 | |||
232 | describe('When rejecting a follower', function () { | ||
233 | const path = '/api/v1/server/followers' | ||
234 | |||
235 | it('Should fail with an invalid token', async function () { | ||
236 | await makePostBodyRequest({ | ||
237 | url: server.url, | ||
238 | path: path + '/toto@localhost:9002/reject', | ||
239 | token: 'fake_token', | ||
240 | statusCodeExpected: 401 | ||
241 | }) | ||
242 | }) | ||
243 | |||
244 | it('Should fail if the user is not an administrator', async function () { | ||
245 | await makePostBodyRequest({ | ||
246 | url: server.url, | ||
247 | path: path + '/toto@localhost:9002/reject', | ||
248 | token: userAccessToken, | ||
249 | statusCodeExpected: 403 | ||
250 | }) | ||
251 | }) | ||
252 | |||
253 | it('Should fail with an invalid follower', async function () { | ||
254 | await makePostBodyRequest({ | ||
255 | url: server.url, | ||
256 | path: path + '/toto/reject', | ||
257 | token: server.accessToken, | ||
258 | statusCodeExpected: 400 | ||
259 | }) | ||
260 | }) | ||
261 | |||
262 | it('Should fail with an unknown follower', async function () { | ||
263 | await makePostBodyRequest({ | ||
264 | url: server.url, | ||
265 | path: path + '/toto@localhost:9003/reject', | ||
266 | token: server.accessToken, | ||
267 | statusCodeExpected: 404 | ||
268 | }) | ||
269 | }) | ||
270 | }) | ||
271 | |||
147 | describe('When removing following', function () { | 272 | describe('When removing following', function () { |
148 | const path = '/api/v1/server/following' | 273 | const path = '/api/v1/server/following' |
149 | 274 | ||
@@ -177,11 +302,6 @@ describe('Test server follows API validators', function () { | |||
177 | }) | 302 | }) |
178 | 303 | ||
179 | after(async function () { | 304 | after(async function () { |
180 | killallServers([ server ]) | 305 | await cleanupTests([ server ]) |
181 | |||
182 | // Keep the logs if the test failed | ||
183 | if (this['ok']) { | ||
184 | await flushTests() | ||
185 | } | ||
186 | }) | 306 | }) |
187 | }) | 307 | }) |
diff --git a/server/tests/api/check-params/index.ts b/server/tests/api/check-params/index.ts index 77c17036a..844fa31c5 100644 --- a/server/tests/api/check-params/index.ts +++ b/server/tests/api/check-params/index.ts | |||
@@ -2,8 +2,10 @@ import './accounts' | |||
2 | import './blocklist' | 2 | import './blocklist' |
3 | import './config' | 3 | import './config' |
4 | import './contact-form' | 4 | import './contact-form' |
5 | import './debug' | ||
5 | import './follows' | 6 | import './follows' |
6 | import './jobs' | 7 | import './jobs' |
8 | import './logs' | ||
7 | import './redundancy' | 9 | import './redundancy' |
8 | import './search' | 10 | import './search' |
9 | import './services' | 11 | import './services' |
@@ -16,6 +18,7 @@ import './video-captions' | |||
16 | import './video-channels' | 18 | import './video-channels' |
17 | import './video-comments' | 19 | import './video-comments' |
18 | import './video-imports' | 20 | import './video-imports' |
21 | import './video-playlists' | ||
19 | import './videos' | 22 | import './videos' |
20 | import './videos-filter' | 23 | import './videos-filter' |
21 | import './videos-history' | 24 | import './videos-history' |
diff --git a/server/tests/api/check-params/jobs.ts b/server/tests/api/check-params/jobs.ts index 89760ff98..c70139514 100644 --- a/server/tests/api/check-params/jobs.ts +++ b/server/tests/api/check-params/jobs.ts | |||
@@ -6,17 +6,18 @@ import { | |||
6 | createUser, | 6 | createUser, |
7 | flushTests, | 7 | flushTests, |
8 | killallServers, | 8 | killallServers, |
9 | runServer, | 9 | flushAndRunServer, |
10 | ServerInfo, | 10 | ServerInfo, |
11 | setAccessTokensToServers, | 11 | setAccessTokensToServers, |
12 | userLogin | 12 | userLogin, |
13 | } from '../../../../shared/utils' | 13 | cleanupTests |
14 | } from '../../../../shared/extra-utils' | ||
14 | import { | 15 | import { |
15 | checkBadCountPagination, | 16 | checkBadCountPagination, |
16 | checkBadSortPagination, | 17 | checkBadSortPagination, |
17 | checkBadStartPagination | 18 | checkBadStartPagination |
18 | } from '../../../../shared/utils/requests/check-api-params' | 19 | } from '../../../../shared/extra-utils/requests/check-api-params' |
19 | import { makeGetRequest } from '../../../../shared/utils/requests/requests' | 20 | import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' |
20 | 21 | ||
21 | describe('Test jobs API validators', function () { | 22 | describe('Test jobs API validators', function () { |
22 | const path = '/api/v1/jobs/failed' | 23 | const path = '/api/v1/jobs/failed' |
@@ -28,9 +29,7 @@ describe('Test jobs API validators', function () { | |||
28 | before(async function () { | 29 | before(async function () { |
29 | this.timeout(120000) | 30 | this.timeout(120000) |
30 | 31 | ||
31 | await flushTests() | 32 | server = await flushAndRunServer(1) |
32 | |||
33 | server = await runServer(1) | ||
34 | 33 | ||
35 | await setAccessTokensToServers([ server ]) | 34 | await setAccessTokensToServers([ server ]) |
36 | 35 | ||
@@ -38,7 +37,7 @@ describe('Test jobs API validators', function () { | |||
38 | username: 'user1', | 37 | username: 'user1', |
39 | password: 'my super password' | 38 | password: 'my super password' |
40 | } | 39 | } |
41 | await createUser(server.url, server.accessToken, user.username, user.password) | 40 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
42 | userAccessToken = await userLogin(server, user) | 41 | userAccessToken = await userLogin(server, user) |
43 | }) | 42 | }) |
44 | 43 | ||
@@ -83,11 +82,6 @@ describe('Test jobs API validators', function () { | |||
83 | }) | 82 | }) |
84 | 83 | ||
85 | after(async function () { | 84 | after(async function () { |
86 | killallServers([ server ]) | 85 | await cleanupTests([ server ]) |
87 | |||
88 | // Keep the logs if the test failed | ||
89 | if (this['ok']) { | ||
90 | await flushTests() | ||
91 | } | ||
92 | }) | 86 | }) |
93 | }) | 87 | }) |
diff --git a/server/tests/api/check-params/logs.ts b/server/tests/api/check-params/logs.ts new file mode 100644 index 000000000..f9d96bcc0 --- /dev/null +++ b/server/tests/api/check-params/logs.ts | |||
@@ -0,0 +1,111 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import 'mocha' | ||
4 | |||
5 | import { | ||
6 | createUser, | ||
7 | flushTests, | ||
8 | killallServers, | ||
9 | flushAndRunServer, | ||
10 | ServerInfo, | ||
11 | setAccessTokensToServers, | ||
12 | userLogin, | ||
13 | cleanupTests | ||
14 | } from '../../../../shared/extra-utils' | ||
15 | import { makeGetRequest } from '../../../../shared/extra-utils/requests/requests' | ||
16 | |||
17 | describe('Test logs API validators', function () { | ||
18 | const path = '/api/v1/server/logs' | ||
19 | let server: ServerInfo | ||
20 | let userAccessToken = '' | ||
21 | |||
22 | // --------------------------------------------------------------- | ||
23 | |||
24 | before(async function () { | ||
25 | this.timeout(120000) | ||
26 | |||
27 | server = await flushAndRunServer(1) | ||
28 | |||
29 | await setAccessTokensToServers([ server ]) | ||
30 | |||
31 | const user = { | ||
32 | username: 'user1', | ||
33 | password: 'my super password' | ||
34 | } | ||
35 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) | ||
36 | userAccessToken = await userLogin(server, user) | ||
37 | }) | ||
38 | |||
39 | describe('When getting logs', function () { | ||
40 | |||
41 | it('Should fail with a non authenticated user', async function () { | ||
42 | await makeGetRequest({ | ||
43 | url: server.url, | ||
44 | path, | ||
45 | statusCodeExpected: 401 | ||
46 | }) | ||
47 | }) | ||
48 | |||
49 | it('Should fail with a non admin user', async function () { | ||
50 | await makeGetRequest({ | ||
51 | url: server.url, | ||
52 | path, | ||
53 | token: userAccessToken, | ||
54 | statusCodeExpected: 403 | ||
55 | }) | ||
56 | }) | ||
57 | |||
58 | it('Should fail with a missing startDate query', async function () { | ||
59 | await makeGetRequest({ | ||
60 | url: server.url, | ||
61 | path, | ||
62 | token: server.accessToken, | ||
63 | statusCodeExpected: 400 | ||
64 | }) | ||
65 | }) | ||
66 | |||
67 | it('Should fail with a bad startDate query', async function () { | ||
68 | await makeGetRequest({ | ||
69 | url: server.url, | ||
70 | path, | ||
71 | token: server.accessToken, | ||
72 | query: { startDate: 'toto' }, | ||
73 | statusCodeExpected: 400 | ||
74 | }) | ||
75 | }) | ||
76 | |||
77 | it('Should fail with a bad endDate query', async function () { | ||
78 | await makeGetRequest({ | ||
79 | url: server.url, | ||
80 | path, | ||
81 | token: server.accessToken, | ||
82 | query: { startDate: new Date().toISOString(), endDate: 'toto' }, | ||
83 | statusCodeExpected: 400 | ||
84 | }) | ||
85 | }) | ||
86 | |||
87 | it('Should fail with a bad level parameter', async function () { | ||
88 | await makeGetRequest({ | ||
89 | url: server.url, | ||
90 | path, | ||
91 | token: server.accessToken, | ||
92 | query: { startDate: new Date().toISOString(), level: 'toto' }, | ||
93 | statusCodeExpected: 400 | ||
94 | }) | ||
95 | }) | ||
96 | |||
97 | it('Should succeed with the correct params', async function () { | ||
98 | await makeGetRequest({ | ||
99 | url: server.url, | ||
100 | path, | ||
101 | token: server.accessToken, | ||
102 | query: { startDate: new Date().toISOString() }, | ||
103 | statusCodeExpected: 200 | ||
104 | }) | ||
105 | }) | ||
106 | }) | ||
107 | |||
108 | after(async function () { | ||
109 | await cleanupTests([ server ]) | ||
110 | }) | ||
111 | }) | ||
diff --git a/server/tests/api/check-params/redundancy.ts b/server/tests/api/check-params/redundancy.ts index ff4726ceb..6471da840 100644 --- a/server/tests/api/check-params/redundancy.ts +++ b/server/tests/api/check-params/redundancy.ts | |||
@@ -3,6 +3,7 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
@@ -12,7 +13,7 @@ import { | |||
12 | ServerInfo, | 13 | ServerInfo, |
13 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
14 | userLogin | 15 | userLogin |
15 | } from '../../../../shared/utils' | 16 | } from '../../../../shared/extra-utils' |
16 | 17 | ||
17 | describe('Test server redundancy API validators', function () { | 18 | describe('Test server redundancy API validators', function () { |
18 | let servers: ServerInfo[] | 19 | let servers: ServerInfo[] |
@@ -23,7 +24,6 @@ describe('Test server redundancy API validators', function () { | |||
23 | before(async function () { | 24 | before(async function () { |
24 | this.timeout(30000) | 25 | this.timeout(30000) |
25 | 26 | ||
26 | await flushTests() | ||
27 | servers = await flushAndRunMultipleServers(2) | 27 | servers = await flushAndRunMultipleServers(2) |
28 | 28 | ||
29 | await setAccessTokensToServers(servers) | 29 | await setAccessTokensToServers(servers) |
@@ -34,7 +34,7 @@ describe('Test server redundancy API validators', function () { | |||
34 | password: 'password' | 34 | password: 'password' |
35 | } | 35 | } |
36 | 36 | ||
37 | await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) | 37 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) |
38 | userAccessToken = await userLogin(servers[0], user) | 38 | userAccessToken = await userLogin(servers[0], user) |
39 | }) | 39 | }) |
40 | 40 | ||
@@ -44,7 +44,7 @@ describe('Test server redundancy API validators', function () { | |||
44 | it('Should fail with an invalid token', async function () { | 44 | it('Should fail with an invalid token', async function () { |
45 | await makePutBodyRequest({ | 45 | await makePutBodyRequest({ |
46 | url: servers[0].url, | 46 | url: servers[0].url, |
47 | path: path + '/localhost:9002', | 47 | path: path + '/localhost:' + servers[1].port, |
48 | fields: { redundancyAllowed: true }, | 48 | fields: { redundancyAllowed: true }, |
49 | token: 'fake_token', | 49 | token: 'fake_token', |
50 | statusCodeExpected: 401 | 50 | statusCodeExpected: 401 |
@@ -54,7 +54,7 @@ describe('Test server redundancy API validators', function () { | |||
54 | it('Should fail if the user is not an administrator', async function () { | 54 | it('Should fail if the user is not an administrator', async function () { |
55 | await makePutBodyRequest({ | 55 | await makePutBodyRequest({ |
56 | url: servers[0].url, | 56 | url: servers[0].url, |
57 | path: path + '/localhost:9002', | 57 | path: path + '/localhost:' + servers[1].port, |
58 | fields: { redundancyAllowed: true }, | 58 | fields: { redundancyAllowed: true }, |
59 | token: userAccessToken, | 59 | token: userAccessToken, |
60 | statusCodeExpected: 403 | 60 | statusCodeExpected: 403 |
@@ -74,7 +74,7 @@ describe('Test server redundancy API validators', function () { | |||
74 | it('Should fail without de redundancyAllowed param', async function () { | 74 | it('Should fail without de redundancyAllowed param', async function () { |
75 | await makePutBodyRequest({ | 75 | await makePutBodyRequest({ |
76 | url: servers[0].url, | 76 | url: servers[0].url, |
77 | path: path + '/localhost:9002', | 77 | path: path + '/localhost:' + servers[1].port, |
78 | fields: { blabla: true }, | 78 | fields: { blabla: true }, |
79 | token: servers[0].accessToken, | 79 | token: servers[0].accessToken, |
80 | statusCodeExpected: 400 | 80 | statusCodeExpected: 400 |
@@ -84,7 +84,7 @@ describe('Test server redundancy API validators', function () { | |||
84 | it('Should succeed with the correct parameters', async function () { | 84 | it('Should succeed with the correct parameters', async function () { |
85 | await makePutBodyRequest({ | 85 | await makePutBodyRequest({ |
86 | url: servers[0].url, | 86 | url: servers[0].url, |
87 | path: path + '/localhost:9002', | 87 | path: path + '/localhost:' + servers[1].port, |
88 | fields: { redundancyAllowed: true }, | 88 | fields: { redundancyAllowed: true }, |
89 | token: servers[0].accessToken, | 89 | token: servers[0].accessToken, |
90 | statusCodeExpected: 204 | 90 | statusCodeExpected: 204 |
@@ -93,11 +93,6 @@ describe('Test server redundancy API validators', function () { | |||
93 | }) | 93 | }) |
94 | 94 | ||
95 | after(async function () { | 95 | after(async function () { |
96 | killallServers(servers) | 96 | await cleanupTests(servers) |
97 | |||
98 | // Keep the logs if the test failed | ||
99 | if (this['ok']) { | ||
100 | await flushTests() | ||
101 | } | ||
102 | }) | 97 | }) |
103 | }) | 98 | }) |
diff --git a/server/tests/api/check-params/search.ts b/server/tests/api/check-params/search.ts index aa81965f3..8ad9d98bf 100644 --- a/server/tests/api/check-params/search.ts +++ b/server/tests/api/check-params/search.ts | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { flushTests, immutableAssign, killallServers, makeGetRequest, runServer, ServerInfo } from '../../../../shared/utils' | 5 | import { cleanupTests, flushAndRunServer, immutableAssign, makeGetRequest, ServerInfo } from '../../../../shared/extra-utils' |
6 | import { | 6 | import { |
7 | checkBadCountPagination, | 7 | checkBadCountPagination, |
8 | checkBadSortPagination, | 8 | checkBadSortPagination, |
9 | checkBadStartPagination | 9 | checkBadStartPagination |
10 | } from '../../../../shared/utils/requests/check-api-params' | 10 | } from '../../../../shared/extra-utils/requests/check-api-params' |
11 | 11 | ||
12 | describe('Test videos API validator', function () { | 12 | describe('Test videos API validator', function () { |
13 | let server: ServerInfo | 13 | let server: ServerInfo |
@@ -17,9 +17,7 @@ describe('Test videos API validator', function () { | |||
17 | before(async function () { | 17 | before(async function () { |
18 | this.timeout(30000) | 18 | this.timeout(30000) |
19 | 19 | ||
20 | await flushTests() | 20 | server = await flushAndRunServer(1) |
21 | |||
22 | server = await runServer(1) | ||
23 | }) | 21 | }) |
24 | 22 | ||
25 | describe('When searching videos', function () { | 23 | describe('When searching videos', function () { |
@@ -113,6 +111,12 @@ describe('Test videos API validator', function () { | |||
113 | 111 | ||
114 | const customQuery2 = immutableAssign(query, { endDate: 'hello' }) | 112 | const customQuery2 = immutableAssign(query, { endDate: 'hello' }) |
115 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 400 }) | 113 | await makeGetRequest({ url: server.url, path, query: customQuery2, statusCodeExpected: 400 }) |
114 | |||
115 | const customQuery3 = immutableAssign(query, { originallyPublishedStartDate: 'hello' }) | ||
116 | await makeGetRequest({ url: server.url, path, query: customQuery3, statusCodeExpected: 400 }) | ||
117 | |||
118 | const customQuery4 = immutableAssign(query, { originallyPublishedEndDate: 'hello' }) | ||
119 | await makeGetRequest({ url: server.url, path, query: customQuery4, statusCodeExpected: 400 }) | ||
116 | }) | 120 | }) |
117 | }) | 121 | }) |
118 | 122 | ||
@@ -141,11 +145,6 @@ describe('Test videos API validator', function () { | |||
141 | }) | 145 | }) |
142 | 146 | ||
143 | after(async function () { | 147 | after(async function () { |
144 | killallServers([ server ]) | 148 | await cleanupTests([ server ]) |
145 | |||
146 | // Keep the logs if the test failed | ||
147 | if (this['ok']) { | ||
148 | await flushTests() | ||
149 | } | ||
150 | }) | 149 | }) |
151 | }) | 150 | }) |
diff --git a/server/tests/api/check-params/services.ts b/server/tests/api/check-params/services.ts index 28591af9d..d15753aed 100644 --- a/server/tests/api/check-params/services.ts +++ b/server/tests/api/check-params/services.ts | |||
@@ -3,14 +3,13 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | flushTests, | 6 | cleanupTests, |
7 | killallServers, | 7 | flushAndRunServer, |
8 | makeGetRequest, | 8 | makeGetRequest, |
9 | runServer, | ||
10 | ServerInfo, | 9 | ServerInfo, |
11 | setAccessTokensToServers, | 10 | setAccessTokensToServers, |
12 | uploadVideo | 11 | uploadVideo |
13 | } from '../../../../shared/utils' | 12 | } from '../../../../shared/extra-utils' |
14 | 13 | ||
15 | describe('Test services API validators', function () { | 14 | describe('Test services API validators', function () { |
16 | let server: ServerInfo | 15 | let server: ServerInfo |
@@ -20,9 +19,7 @@ describe('Test services API validators', function () { | |||
20 | before(async function () { | 19 | before(async function () { |
21 | this.timeout(60000) | 20 | this.timeout(60000) |
22 | 21 | ||
23 | await flushTests() | 22 | server = await flushAndRunServer(1) |
24 | |||
25 | server = await runServer(1) | ||
26 | await setAccessTokensToServers([ server ]) | 23 | await setAccessTokensToServers([ server ]) |
27 | 24 | ||
28 | const res = await uploadVideo(server.url, server.accessToken, { name: 'my super name' }) | 25 | const res = await uploadVideo(server.url, server.accessToken, { name: 'my super name' }) |
@@ -42,47 +39,47 @@ describe('Test services API validators', function () { | |||
42 | }) | 39 | }) |
43 | 40 | ||
44 | it('Should fail with an invalid video id', async function () { | 41 | it('Should fail with an invalid video id', async function () { |
45 | const embedUrl = 'http://localhost:9001/videos/watch/blabla' | 42 | const embedUrl = `http://localhost:${server.port}/videos/watch/blabla` |
46 | await checkParamEmbed(server, embedUrl) | 43 | await checkParamEmbed(server, embedUrl) |
47 | }) | 44 | }) |
48 | 45 | ||
49 | it('Should fail with an unknown video', async function () { | 46 | it('Should fail with an unknown video', async function () { |
50 | const embedUrl = 'http://localhost:9001/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c' | 47 | const embedUrl = `http://localhost:${server.port}/videos/watch/88fc0165-d1f0-4a35-a51a-3b47f668689c` |
51 | await checkParamEmbed(server, embedUrl, 404) | 48 | await checkParamEmbed(server, embedUrl, 404) |
52 | }) | 49 | }) |
53 | 50 | ||
54 | it('Should fail with an invalid path', async function () { | 51 | it('Should fail with an invalid path', async function () { |
55 | const embedUrl = 'http://localhost:9001/videos/watchs/' + server.video.uuid | 52 | const embedUrl = `http://localhost:${server.port}/videos/watchs/${server.video.uuid}` |
56 | 53 | ||
57 | await checkParamEmbed(server, embedUrl) | 54 | await checkParamEmbed(server, embedUrl) |
58 | }) | 55 | }) |
59 | 56 | ||
60 | it('Should fail with an invalid max height', async function () { | 57 | it('Should fail with an invalid max height', async function () { |
61 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | 58 | const embedUrl = `http://localhost:${server.port}/videos/watch/${server.video.uuid}` |
62 | 59 | ||
63 | await checkParamEmbed(server, embedUrl, 400, { maxheight: 'hello' }) | 60 | await checkParamEmbed(server, embedUrl, 400, { maxheight: 'hello' }) |
64 | }) | 61 | }) |
65 | 62 | ||
66 | it('Should fail with an invalid max width', async function () { | 63 | it('Should fail with an invalid max width', async function () { |
67 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | 64 | const embedUrl = `http://localhost:${server.port}/videos/watch/${server.video.uuid}` |
68 | 65 | ||
69 | await checkParamEmbed(server, embedUrl, 400, { maxwidth: 'hello' }) | 66 | await checkParamEmbed(server, embedUrl, 400, { maxwidth: 'hello' }) |
70 | }) | 67 | }) |
71 | 68 | ||
72 | it('Should fail with an invalid format', async function () { | 69 | it('Should fail with an invalid format', async function () { |
73 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | 70 | const embedUrl = `http://localhost:${server.port}/videos/watch/${server.video.uuid}` |
74 | 71 | ||
75 | await checkParamEmbed(server, embedUrl, 400, { format: 'blabla' }) | 72 | await checkParamEmbed(server, embedUrl, 400, { format: 'blabla' }) |
76 | }) | 73 | }) |
77 | 74 | ||
78 | it('Should fail with a non supported format', async function () { | 75 | it('Should fail with a non supported format', async function () { |
79 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | 76 | const embedUrl = `http://localhost:${server.port}/videos/watch/${server.video.uuid}` |
80 | 77 | ||
81 | await checkParamEmbed(server, embedUrl, 501, { format: 'xml' }) | 78 | await checkParamEmbed(server, embedUrl, 501, { format: 'xml' }) |
82 | }) | 79 | }) |
83 | 80 | ||
84 | it('Should succeed with the correct params', async function () { | 81 | it('Should succeed with the correct params', async function () { |
85 | const embedUrl = 'http://localhost:9001/videos/watch/' + server.video.uuid | 82 | const embedUrl = `http://localhost:${server.port}/videos/watch/${server.video.uuid}` |
86 | const query = { | 83 | const query = { |
87 | format: 'json', | 84 | format: 'json', |
88 | maxheight: 400, | 85 | maxheight: 400, |
@@ -94,12 +91,7 @@ describe('Test services API validators', function () { | |||
94 | }) | 91 | }) |
95 | 92 | ||
96 | after(async function () { | 93 | after(async function () { |
97 | killallServers([ server ]) | 94 | await cleanupTests([ server ]) |
98 | |||
99 | // Keep the logs if the test failed | ||
100 | if (this['ok']) { | ||
101 | await flushTests() | ||
102 | } | ||
103 | }) | 95 | }) |
104 | }) | 96 | }) |
105 | 97 | ||
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts index 714f481e9..14ee20d45 100644 --- a/server/tests/api/check-params/user-notifications.ts +++ b/server/tests/api/check-params/user-notifications.ts | |||
@@ -4,22 +4,21 @@ import 'mocha' | |||
4 | import * as io from 'socket.io-client' | 4 | import * as io from 'socket.io-client' |
5 | 5 | ||
6 | import { | 6 | import { |
7 | flushTests, | 7 | cleanupTests, |
8 | flushAndRunServer, | ||
8 | immutableAssign, | 9 | immutableAssign, |
9 | killallServers, | ||
10 | makeGetRequest, | 10 | makeGetRequest, |
11 | makePostBodyRequest, | 11 | makePostBodyRequest, |
12 | makePutBodyRequest, | 12 | makePutBodyRequest, |
13 | runServer, | ||
14 | ServerInfo, | 13 | ServerInfo, |
15 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
16 | wait | 15 | wait |
17 | } from '../../../../shared/utils' | 16 | } from '../../../../shared/extra-utils' |
18 | import { | 17 | import { |
19 | checkBadCountPagination, | 18 | checkBadCountPagination, |
20 | checkBadSortPagination, | 19 | checkBadSortPagination, |
21 | checkBadStartPagination | 20 | checkBadStartPagination |
22 | } from '../../../../shared/utils/requests/check-api-params' | 21 | } from '../../../../shared/extra-utils/requests/check-api-params' |
23 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../../shared/models/users' | 22 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../../shared/models/users' |
24 | 23 | ||
25 | describe('Test user notifications API validators', function () { | 24 | describe('Test user notifications API validators', function () { |
@@ -30,9 +29,7 @@ describe('Test user notifications API validators', function () { | |||
30 | before(async function () { | 29 | before(async function () { |
31 | this.timeout(30000) | 30 | this.timeout(30000) |
32 | 31 | ||
33 | await flushTests() | 32 | server = await flushAndRunServer(1) |
34 | |||
35 | server = await runServer(1) | ||
36 | 33 | ||
37 | await setAccessTokensToServers([ server ]) | 34 | await setAccessTokensToServers([ server ]) |
38 | }) | 35 | }) |
@@ -168,12 +165,14 @@ describe('Test user notifications API validators', function () { | |||
168 | newVideoFromSubscription: UserNotificationSettingValue.WEB, | 165 | newVideoFromSubscription: UserNotificationSettingValue.WEB, |
169 | newCommentOnMyVideo: UserNotificationSettingValue.WEB, | 166 | newCommentOnMyVideo: UserNotificationSettingValue.WEB, |
170 | videoAbuseAsModerator: UserNotificationSettingValue.WEB, | 167 | videoAbuseAsModerator: UserNotificationSettingValue.WEB, |
168 | videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB, | ||
171 | blacklistOnMyVideo: UserNotificationSettingValue.WEB, | 169 | blacklistOnMyVideo: UserNotificationSettingValue.WEB, |
172 | myVideoImportFinished: UserNotificationSettingValue.WEB, | 170 | myVideoImportFinished: UserNotificationSettingValue.WEB, |
173 | myVideoPublished: UserNotificationSettingValue.WEB, | 171 | myVideoPublished: UserNotificationSettingValue.WEB, |
174 | commentMention: UserNotificationSettingValue.WEB, | 172 | commentMention: UserNotificationSettingValue.WEB, |
175 | newFollow: UserNotificationSettingValue.WEB, | 173 | newFollow: UserNotificationSettingValue.WEB, |
176 | newUserRegistration: UserNotificationSettingValue.WEB | 174 | newUserRegistration: UserNotificationSettingValue.WEB, |
175 | newInstanceFollower: UserNotificationSettingValue.WEB | ||
177 | } | 176 | } |
178 | 177 | ||
179 | it('Should fail with missing fields', async function () { | 178 | it('Should fail with missing fields', async function () { |
@@ -234,7 +233,7 @@ describe('Test user notifications API validators', function () { | |||
234 | 233 | ||
235 | describe('When connecting to my notification socket', function () { | 234 | describe('When connecting to my notification socket', function () { |
236 | it('Should fail with no token', function (next) { | 235 | it('Should fail with no token', function (next) { |
237 | const socket = io('http://localhost:9001/user-notifications', { reconnection: false }) | 236 | const socket = io(`http://localhost:${server.port}/user-notifications`, { reconnection: false }) |
238 | 237 | ||
239 | socket.on('error', () => { | 238 | socket.on('error', () => { |
240 | socket.removeListener('error', this) | 239 | socket.removeListener('error', this) |
@@ -249,7 +248,7 @@ describe('Test user notifications API validators', function () { | |||
249 | }) | 248 | }) |
250 | 249 | ||
251 | it('Should fail with an invalid token', function (next) { | 250 | it('Should fail with an invalid token', function (next) { |
252 | const socket = io('http://localhost:9001/user-notifications', { | 251 | const socket = io(`http://localhost:${server.port}/user-notifications`, { |
253 | query: { accessToken: 'bad_access_token' }, | 252 | query: { accessToken: 'bad_access_token' }, |
254 | reconnection: false | 253 | reconnection: false |
255 | }) | 254 | }) |
@@ -267,7 +266,7 @@ describe('Test user notifications API validators', function () { | |||
267 | }) | 266 | }) |
268 | 267 | ||
269 | it('Should success with the correct token', function (next) { | 268 | it('Should success with the correct token', function (next) { |
270 | const socket = io('http://localhost:9001/user-notifications', { | 269 | const socket = io(`http://localhost:${server.port}/user-notifications`, { |
271 | query: { accessToken: server.accessToken }, | 270 | query: { accessToken: server.accessToken }, |
272 | reconnection: false | 271 | reconnection: false |
273 | }) | 272 | }) |
@@ -287,11 +286,6 @@ describe('Test user notifications API validators', function () { | |||
287 | }) | 286 | }) |
288 | 287 | ||
289 | after(async function () { | 288 | after(async function () { |
290 | killallServers([ server ]) | 289 | await cleanupTests([ server ]) |
291 | |||
292 | // Keep the logs if the test failed | ||
293 | if (this['ok']) { | ||
294 | await flushTests() | ||
295 | } | ||
296 | }) | 290 | }) |
297 | }) | 291 | }) |
diff --git a/server/tests/api/check-params/user-subscriptions.ts b/server/tests/api/check-params/user-subscriptions.ts index 8a9ced7c1..fa36c4078 100644 --- a/server/tests/api/check-params/user-subscriptions.ts +++ b/server/tests/api/check-params/user-subscriptions.ts | |||
@@ -3,24 +3,23 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | flushTests, | 8 | flushAndRunServer, |
8 | killallServers, | ||
9 | makeDeleteRequest, | 9 | makeDeleteRequest, |
10 | makeGetRequest, | 10 | makeGetRequest, |
11 | makePostBodyRequest, | 11 | makePostBodyRequest, |
12 | runServer, | ||
13 | ServerInfo, | 12 | ServerInfo, |
14 | setAccessTokensToServers, | 13 | setAccessTokensToServers, |
15 | userLogin | 14 | userLogin |
16 | } from '../../../../shared/utils' | 15 | } from '../../../../shared/extra-utils' |
17 | 16 | ||
18 | import { | 17 | import { |
19 | checkBadCountPagination, | 18 | checkBadCountPagination, |
20 | checkBadSortPagination, | 19 | checkBadSortPagination, |
21 | checkBadStartPagination | 20 | checkBadStartPagination |
22 | } from '../../../../shared/utils/requests/check-api-params' | 21 | } from '../../../../shared/extra-utils/requests/check-api-params' |
23 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 22 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
24 | 23 | ||
25 | describe('Test user subscriptions API validators', function () { | 24 | describe('Test user subscriptions API validators', function () { |
26 | const path = '/api/v1/users/me/subscriptions' | 25 | const path = '/api/v1/users/me/subscriptions' |
@@ -32,9 +31,7 @@ describe('Test user subscriptions API validators', function () { | |||
32 | before(async function () { | 31 | before(async function () { |
33 | this.timeout(30000) | 32 | this.timeout(30000) |
34 | 33 | ||
35 | await flushTests() | 34 | server = await flushAndRunServer(1) |
36 | |||
37 | server = await runServer(1) | ||
38 | 35 | ||
39 | await setAccessTokensToServers([ server ]) | 36 | await setAccessTokensToServers([ server ]) |
40 | 37 | ||
@@ -42,7 +39,7 @@ describe('Test user subscriptions API validators', function () { | |||
42 | username: 'user1', | 39 | username: 'user1', |
43 | password: 'my super password' | 40 | password: 'my super password' |
44 | } | 41 | } |
45 | await createUser(server.url, server.accessToken, user.username, user.password) | 42 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
46 | userAccessToken = await userLogin(server, user) | 43 | userAccessToken = await userLogin(server, user) |
47 | }) | 44 | }) |
48 | 45 | ||
@@ -115,7 +112,7 @@ describe('Test user subscriptions API validators', function () { | |||
115 | await makePostBodyRequest({ | 112 | await makePostBodyRequest({ |
116 | url: server.url, | 113 | url: server.url, |
117 | path, | 114 | path, |
118 | fields: { uri: 'user1_channel@localhost:9001' }, | 115 | fields: { uri: 'user1_channel@localhost:' + server.port }, |
119 | statusCodeExpected: 401 | 116 | statusCodeExpected: 401 |
120 | }) | 117 | }) |
121 | }) | 118 | }) |
@@ -153,7 +150,7 @@ describe('Test user subscriptions API validators', function () { | |||
153 | url: server.url, | 150 | url: server.url, |
154 | path, | 151 | path, |
155 | token: server.accessToken, | 152 | token: server.accessToken, |
156 | fields: { uri: 'user1_channel@localhost:9001' }, | 153 | fields: { uri: 'user1_channel@localhost:' + server.port }, |
157 | statusCodeExpected: 204 | 154 | statusCodeExpected: 204 |
158 | }) | 155 | }) |
159 | 156 | ||
@@ -165,7 +162,7 @@ describe('Test user subscriptions API validators', function () { | |||
165 | it('Should fail with a non authenticated user', async function () { | 162 | it('Should fail with a non authenticated user', async function () { |
166 | await makeGetRequest({ | 163 | await makeGetRequest({ |
167 | url: server.url, | 164 | url: server.url, |
168 | path: path + '/user1_channel@localhost:9001', | 165 | path: path + '/user1_channel@localhost:' + server.port, |
169 | statusCodeExpected: 401 | 166 | statusCodeExpected: 401 |
170 | }) | 167 | }) |
171 | }) | 168 | }) |
@@ -196,7 +193,7 @@ describe('Test user subscriptions API validators', function () { | |||
196 | it('Should fail with an unknown subscription', async function () { | 193 | it('Should fail with an unknown subscription', async function () { |
197 | await makeGetRequest({ | 194 | await makeGetRequest({ |
198 | url: server.url, | 195 | url: server.url, |
199 | path: path + '/root1@localhost:9001', | 196 | path: path + '/root1@localhost:' + server.port, |
200 | token: server.accessToken, | 197 | token: server.accessToken, |
201 | statusCodeExpected: 404 | 198 | statusCodeExpected: 404 |
202 | }) | 199 | }) |
@@ -205,14 +202,14 @@ describe('Test user subscriptions API validators', function () { | |||
205 | it('Should succeed with the correct parameters', async function () { | 202 | it('Should succeed with the correct parameters', async function () { |
206 | await makeGetRequest({ | 203 | await makeGetRequest({ |
207 | url: server.url, | 204 | url: server.url, |
208 | path: path + '/user1_channel@localhost:9001', | 205 | path: path + '/user1_channel@localhost:' + server.port, |
209 | token: server.accessToken, | 206 | token: server.accessToken, |
210 | statusCodeExpected: 200 | 207 | statusCodeExpected: 200 |
211 | }) | 208 | }) |
212 | }) | 209 | }) |
213 | }) | 210 | }) |
214 | 211 | ||
215 | describe('When checking if subscriptions exist', async function () { | 212 | describe('When checking if subscriptions exist', function () { |
216 | const existPath = path + '/exist' | 213 | const existPath = path + '/exist' |
217 | 214 | ||
218 | it('Should fail with a non authenticated user', async function () { | 215 | it('Should fail with a non authenticated user', async function () { |
@@ -245,7 +242,7 @@ describe('Test user subscriptions API validators', function () { | |||
245 | await makeGetRequest({ | 242 | await makeGetRequest({ |
246 | url: server.url, | 243 | url: server.url, |
247 | path: existPath, | 244 | path: existPath, |
248 | query: { 'uris[]': 'coucou@localhost:9001' }, | 245 | query: { 'uris[]': 'coucou@localhost:' + server.port }, |
249 | token: server.accessToken, | 246 | token: server.accessToken, |
250 | statusCodeExpected: 200 | 247 | statusCodeExpected: 200 |
251 | }) | 248 | }) |
@@ -256,7 +253,7 @@ describe('Test user subscriptions API validators', function () { | |||
256 | it('Should fail with a non authenticated user', async function () { | 253 | it('Should fail with a non authenticated user', async function () { |
257 | await makeDeleteRequest({ | 254 | await makeDeleteRequest({ |
258 | url: server.url, | 255 | url: server.url, |
259 | path: path + '/user1_channel@localhost:9001', | 256 | path: path + '/user1_channel@localhost:' + server.port, |
260 | statusCodeExpected: 401 | 257 | statusCodeExpected: 401 |
261 | }) | 258 | }) |
262 | }) | 259 | }) |
@@ -287,7 +284,7 @@ describe('Test user subscriptions API validators', function () { | |||
287 | it('Should fail with an unknown subscription', async function () { | 284 | it('Should fail with an unknown subscription', async function () { |
288 | await makeDeleteRequest({ | 285 | await makeDeleteRequest({ |
289 | url: server.url, | 286 | url: server.url, |
290 | path: path + '/root1@localhost:9001', | 287 | path: path + '/root1@localhost:' + server.port, |
291 | token: server.accessToken, | 288 | token: server.accessToken, |
292 | statusCodeExpected: 404 | 289 | statusCodeExpected: 404 |
293 | }) | 290 | }) |
@@ -296,7 +293,7 @@ describe('Test user subscriptions API validators', function () { | |||
296 | it('Should succeed with the correct parameters', async function () { | 293 | it('Should succeed with the correct parameters', async function () { |
297 | await makeDeleteRequest({ | 294 | await makeDeleteRequest({ |
298 | url: server.url, | 295 | url: server.url, |
299 | path: path + '/user1_channel@localhost:9001', | 296 | path: path + '/user1_channel@localhost:' + server.port, |
300 | token: server.accessToken, | 297 | token: server.accessToken, |
301 | statusCodeExpected: 204 | 298 | statusCodeExpected: 204 |
302 | }) | 299 | }) |
@@ -304,11 +301,6 @@ describe('Test user subscriptions API validators', function () { | |||
304 | }) | 301 | }) |
305 | 302 | ||
306 | after(async function () { | 303 | after(async function () { |
307 | killallServers([ server ]) | 304 | await cleanupTests([ server ]) |
308 | |||
309 | // Keep the logs if the test failed | ||
310 | if (this['ok']) { | ||
311 | await flushTests() | ||
312 | } | ||
313 | }) | 305 | }) |
314 | }) | 306 | }) |
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index a3e8e2e9c..5935104a5 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -6,19 +6,38 @@ import { join } from 'path' | |||
6 | import { UserRole, VideoImport, VideoImportState } from '../../../../shared' | 6 | import { UserRole, VideoImport, VideoImportState } from '../../../../shared' |
7 | 7 | ||
8 | import { | 8 | import { |
9 | createUser, flushTests, getMyUserInformation, getMyUserVideoRating, getUsersList, immutableAssign, killallServers, makeGetRequest, | 9 | blockUser, |
10 | makePostBodyRequest, makeUploadRequest, makePutBodyRequest, registerUser, removeUser, runServer, ServerInfo, setAccessTokensToServers, | 10 | cleanupTests, |
11 | updateUser, uploadVideo, userLogin, deleteMe, unblockUser, blockUser | 11 | createUser, |
12 | } from '../../../../shared/utils' | 12 | deleteMe, |
13 | flushAndRunServer, | ||
14 | getMyUserInformation, | ||
15 | getMyUserVideoRating, | ||
16 | getUsersList, | ||
17 | immutableAssign, | ||
18 | makeGetRequest, | ||
19 | makePostBodyRequest, | ||
20 | makePutBodyRequest, | ||
21 | makeUploadRequest, | ||
22 | registerUser, | ||
23 | removeUser, | ||
24 | ServerInfo, | ||
25 | setAccessTokensToServers, | ||
26 | unblockUser, | ||
27 | updateUser, | ||
28 | uploadVideo, | ||
29 | userLogin | ||
30 | } from '../../../../shared/extra-utils' | ||
13 | import { | 31 | import { |
14 | checkBadCountPagination, | 32 | checkBadCountPagination, |
15 | checkBadSortPagination, | 33 | checkBadSortPagination, |
16 | checkBadStartPagination | 34 | checkBadStartPagination |
17 | } from '../../../../shared/utils/requests/check-api-params' | 35 | } from '../../../../shared/extra-utils/requests/check-api-params' |
18 | import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../../../shared/utils/videos/video-imports' | 36 | import { getMagnetURI, getMyVideoImports, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' |
19 | import { VideoPrivacy } from '../../../../shared/models/videos' | 37 | import { VideoPrivacy } from '../../../../shared/models/videos' |
20 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 38 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
21 | import { expect } from 'chai' | 39 | import { expect } from 'chai' |
40 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | ||
22 | 41 | ||
23 | describe('Test users API validators', function () { | 42 | describe('Test users API validators', function () { |
24 | const path = '/api/v1/users/' | 43 | const path = '/api/v1/users/' |
@@ -39,15 +58,19 @@ describe('Test users API validators', function () { | |||
39 | before(async function () { | 58 | before(async function () { |
40 | this.timeout(30000) | 59 | this.timeout(30000) |
41 | 60 | ||
42 | await flushTests() | 61 | server = await flushAndRunServer(1) |
43 | 62 | serverWithRegistrationDisabled = await flushAndRunServer(2) | |
44 | server = await runServer(1) | ||
45 | serverWithRegistrationDisabled = await runServer(2) | ||
46 | 63 | ||
47 | await setAccessTokensToServers([ server ]) | 64 | await setAccessTokensToServers([ server ]) |
48 | 65 | ||
49 | const videoQuota = 42000000 | 66 | const videoQuota = 42000000 |
50 | await createUser(server.url, server.accessToken, user.username, user.password, videoQuota) | 67 | await createUser({ |
68 | url: server.url, | ||
69 | accessToken: server.accessToken, | ||
70 | username: user.username, | ||
71 | password: user.password, | ||
72 | videoQuota: videoQuota | ||
73 | }) | ||
51 | userAccessToken = await userLogin(server, user) | 74 | userAccessToken = await userLogin(server, user) |
52 | 75 | ||
53 | { | 76 | { |
@@ -99,7 +122,8 @@ describe('Test users API validators', function () { | |||
99 | password: 'my super password', | 122 | password: 'my super password', |
100 | videoQuota: -1, | 123 | videoQuota: -1, |
101 | videoQuotaDaily: -1, | 124 | videoQuotaDaily: -1, |
102 | role: UserRole.USER | 125 | role: UserRole.USER, |
126 | adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST | ||
103 | } | 127 | } |
104 | 128 | ||
105 | it('Should fail with a too small username', async function () { | 129 | it('Should fail with a too small username', async function () { |
@@ -150,6 +174,12 @@ describe('Test users API validators', function () { | |||
150 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | 174 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) |
151 | }) | 175 | }) |
152 | 176 | ||
177 | it('Should fail with invalid admin flags', async function () { | ||
178 | const fields = immutableAssign(baseCorrectParams, { adminFlags: 'toto' }) | ||
179 | |||
180 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
181 | }) | ||
182 | |||
153 | it('Should fail with an non authenticated user', async function () { | 183 | it('Should fail with an non authenticated user', async function () { |
154 | await makePostBodyRequest({ | 184 | await makePostBodyRequest({ |
155 | url: server.url, | 185 | url: server.url, |
@@ -464,6 +494,24 @@ describe('Test users API validators', function () { | |||
464 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | 494 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) |
465 | }) | 495 | }) |
466 | 496 | ||
497 | it('Should fail with a too small password', async function () { | ||
498 | const fields = { | ||
499 | currentPassword: 'my super password', | ||
500 | password: 'bla' | ||
501 | } | ||
502 | |||
503 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
504 | }) | ||
505 | |||
506 | it('Should fail with a too long password', async function () { | ||
507 | const fields = { | ||
508 | currentPassword: 'my super password', | ||
509 | password: 'super'.repeat(61) | ||
510 | } | ||
511 | |||
512 | await makePutBodyRequest({ url: server.url, path: path + userId, token: server.accessToken, fields }) | ||
513 | }) | ||
514 | |||
467 | it('Should fail with an non authenticated user', async function () { | 515 | it('Should fail with an non authenticated user', async function () { |
468 | const fields = { | 516 | const fields = { |
469 | videoQuota: 42 | 517 | videoQuota: 42 |
@@ -480,6 +528,12 @@ describe('Test users API validators', function () { | |||
480 | await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields }) | 528 | await makePutBodyRequest({ url: server.url, path: path + rootId, token: server.accessToken, fields }) |
481 | }) | 529 | }) |
482 | 530 | ||
531 | it('Should fail with invalid admin flags', async function () { | ||
532 | const fields = { adminFlags: 'toto' } | ||
533 | |||
534 | await makePostBodyRequest({ url: server.url, path, token: server.accessToken, fields }) | ||
535 | }) | ||
536 | |||
483 | it('Should succeed with the correct params', async function () { | 537 | it('Should succeed with the correct params', async function () { |
484 | const fields = { | 538 | const fields = { |
485 | email: 'email@example.com', | 539 | email: 'email@example.com', |
@@ -520,6 +574,38 @@ describe('Test users API validators', function () { | |||
520 | }) | 574 | }) |
521 | }) | 575 | }) |
522 | 576 | ||
577 | describe('When retrieving my global ratings', function () { | ||
578 | const path = '/api/v1/accounts/user1/ratings' | ||
579 | |||
580 | it('Should fail with a bad start pagination', async function () { | ||
581 | await checkBadStartPagination(server.url, path, userAccessToken) | ||
582 | }) | ||
583 | |||
584 | it('Should fail with a bad count pagination', async function () { | ||
585 | await checkBadCountPagination(server.url, path, userAccessToken) | ||
586 | }) | ||
587 | |||
588 | it('Should fail with an incorrect sort', async function () { | ||
589 | await checkBadSortPagination(server.url, path, userAccessToken) | ||
590 | }) | ||
591 | |||
592 | it('Should fail with a unauthenticated user', async function () { | ||
593 | await makeGetRequest({ url: server.url, path, statusCodeExpected: 401 }) | ||
594 | }) | ||
595 | |||
596 | it('Should fail with a another user', async function () { | ||
597 | await makeGetRequest({ url: server.url, path, token: server.accessToken, statusCodeExpected: 403 }) | ||
598 | }) | ||
599 | |||
600 | it('Should fail with a bad type', async function () { | ||
601 | await makeGetRequest({ url: server.url, path, token: userAccessToken, query: { rating: 'toto ' }, statusCodeExpected: 400 }) | ||
602 | }) | ||
603 | |||
604 | it('Should succeed with the correct params', async function () { | ||
605 | await makeGetRequest({ url: server.url, path, token: userAccessToken, statusCodeExpected: 200 }) | ||
606 | }) | ||
607 | }) | ||
608 | |||
523 | describe('When blocking/unblocking/removing user', function () { | 609 | describe('When blocking/unblocking/removing user', function () { |
524 | it('Should fail with an incorrect id', async function () { | 610 | it('Should fail with an incorrect id', async function () { |
525 | await removeUser(server.url, 'blabla', server.accessToken, 400) | 611 | await removeUser(server.url, 'blabla', server.accessToken, 400) |
@@ -627,7 +713,7 @@ describe('Test users API validators', function () { | |||
627 | }) | 713 | }) |
628 | 714 | ||
629 | it('Should fail if we register a user with the same email', async function () { | 715 | it('Should fail if we register a user with the same email', async function () { |
630 | const fields = immutableAssign(baseCorrectParams, { email: 'admin1@example.com' }) | 716 | const fields = immutableAssign(baseCorrectParams, { email: 'admin' + server.internalServerNumber + '@example.com' }) |
631 | 717 | ||
632 | await makePostBodyRequest({ | 718 | await makePostBodyRequest({ |
633 | url: server.url, | 719 | url: server.url, |
@@ -812,11 +898,6 @@ describe('Test users API validators', function () { | |||
812 | }) | 898 | }) |
813 | 899 | ||
814 | after(async function () { | 900 | after(async function () { |
815 | killallServers([ server, serverWithRegistrationDisabled ]) | 901 | await cleanupTests([ server, serverWithRegistrationDisabled ]) |
816 | |||
817 | // Keep the logs if the test failed | ||
818 | if (this['ok']) { | ||
819 | await flushTests() | ||
820 | } | ||
821 | }) | 902 | }) |
822 | }) | 903 | }) |
diff --git a/server/tests/api/check-params/video-abuses.ts b/server/tests/api/check-params/video-abuses.ts index 3b8f5f14d..bf29f8d4d 100644 --- a/server/tests/api/check-params/video-abuses.ts +++ b/server/tests/api/check-params/video-abuses.ts | |||
@@ -3,24 +3,23 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | deleteVideoAbuse, | 8 | deleteVideoAbuse, |
8 | flushTests, | 9 | flushAndRunServer, |
9 | killallServers, | ||
10 | makeGetRequest, | 10 | makeGetRequest, |
11 | makePostBodyRequest, | 11 | makePostBodyRequest, |
12 | runServer, | ||
13 | ServerInfo, | 12 | ServerInfo, |
14 | setAccessTokensToServers, | 13 | setAccessTokensToServers, |
15 | updateVideoAbuse, | 14 | updateVideoAbuse, |
16 | uploadVideo, | 15 | uploadVideo, |
17 | userLogin | 16 | userLogin |
18 | } from '../../../../shared/utils' | 17 | } from '../../../../shared/extra-utils' |
19 | import { | 18 | import { |
20 | checkBadCountPagination, | 19 | checkBadCountPagination, |
21 | checkBadSortPagination, | 20 | checkBadSortPagination, |
22 | checkBadStartPagination | 21 | checkBadStartPagination |
23 | } from '../../../../shared/utils/requests/check-api-params' | 22 | } from '../../../../shared/extra-utils/requests/check-api-params' |
24 | import { VideoAbuseState } from '../../../../shared/models/videos' | 23 | import { VideoAbuseState } from '../../../../shared/models/videos' |
25 | 24 | ||
26 | describe('Test video abuses API validators', function () { | 25 | describe('Test video abuses API validators', function () { |
@@ -33,15 +32,13 @@ describe('Test video abuses API validators', function () { | |||
33 | before(async function () { | 32 | before(async function () { |
34 | this.timeout(30000) | 33 | this.timeout(30000) |
35 | 34 | ||
36 | await flushTests() | 35 | server = await flushAndRunServer(1) |
37 | |||
38 | server = await runServer(1) | ||
39 | 36 | ||
40 | await setAccessTokensToServers([ server ]) | 37 | await setAccessTokensToServers([ server ]) |
41 | 38 | ||
42 | const username = 'user1' | 39 | const username = 'user1' |
43 | const password = 'my super password' | 40 | const password = 'my super password' |
44 | await createUser(server.url, server.accessToken, username, password) | 41 | await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) |
45 | userAccessToken = await userLogin(server, { username, password }) | 42 | userAccessToken = await userLogin(server, { username, password }) |
46 | 43 | ||
47 | const res = await uploadVideo(server.url, server.accessToken, {}) | 44 | const res = await uploadVideo(server.url, server.accessToken, {}) |
@@ -191,11 +188,6 @@ describe('Test video abuses API validators', function () { | |||
191 | }) | 188 | }) |
192 | 189 | ||
193 | after(async function () { | 190 | after(async function () { |
194 | killallServers([ server ]) | 191 | await cleanupTests([ server ]) |
195 | |||
196 | // Keep the logs if the test failed | ||
197 | if (this['ok']) { | ||
198 | await flushTests() | ||
199 | } | ||
200 | }) | 192 | }) |
201 | }) | 193 | }) |
diff --git a/server/tests/api/check-params/video-blacklist.ts b/server/tests/api/check-params/video-blacklist.ts index 6b82643f4..6466888fb 100644 --- a/server/tests/api/check-params/video-blacklist.ts +++ b/server/tests/api/check-params/video-blacklist.ts | |||
@@ -3,6 +3,7 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | 4 | ||
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
@@ -18,13 +19,13 @@ import { | |||
18 | setAccessTokensToServers, | 19 | setAccessTokensToServers, |
19 | uploadVideo, | 20 | uploadVideo, |
20 | userLogin, waitJobs | 21 | userLogin, waitJobs |
21 | } from '../../../../shared/utils' | 22 | } from '../../../../shared/extra-utils' |
22 | import { | 23 | import { |
23 | checkBadCountPagination, | 24 | checkBadCountPagination, |
24 | checkBadSortPagination, | 25 | checkBadSortPagination, |
25 | checkBadStartPagination | 26 | checkBadStartPagination |
26 | } from '../../../../shared/utils/requests/check-api-params' | 27 | } from '../../../../shared/extra-utils/requests/check-api-params' |
27 | import { VideoDetails } from '../../../../shared/models/videos' | 28 | import { VideoDetails, VideoBlacklistType } from '../../../../shared/models/videos' |
28 | import { expect } from 'chai' | 29 | import { expect } from 'chai' |
29 | 30 | ||
30 | describe('Test video blacklist API validators', function () { | 31 | describe('Test video blacklist API validators', function () { |
@@ -39,7 +40,6 @@ describe('Test video blacklist API validators', function () { | |||
39 | before(async function () { | 40 | before(async function () { |
40 | this.timeout(120000) | 41 | this.timeout(120000) |
41 | 42 | ||
42 | await flushTests() | ||
43 | servers = await flushAndRunMultipleServers(2) | 43 | servers = await flushAndRunMultipleServers(2) |
44 | 44 | ||
45 | await setAccessTokensToServers(servers) | 45 | await setAccessTokensToServers(servers) |
@@ -48,14 +48,14 @@ describe('Test video blacklist API validators', function () { | |||
48 | { | 48 | { |
49 | const username = 'user1' | 49 | const username = 'user1' |
50 | const password = 'my super password' | 50 | const password = 'my super password' |
51 | await createUser(servers[0].url, servers[0].accessToken, username, password) | 51 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password }) |
52 | userAccessToken1 = await userLogin(servers[0], { username, password }) | 52 | userAccessToken1 = await userLogin(servers[0], { username, password }) |
53 | } | 53 | } |
54 | 54 | ||
55 | { | 55 | { |
56 | const username = 'user2' | 56 | const username = 'user2' |
57 | const password = 'my super password' | 57 | const password = 'my super password' |
58 | await createUser(servers[0].url, servers[0].accessToken, username, password) | 58 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: username, password: password }) |
59 | userAccessToken2 = await userLogin(servers[0], { username, password }) | 59 | userAccessToken2 = await userLogin(servers[0], { username, password }) |
60 | } | 60 | } |
61 | 61 | ||
@@ -220,11 +220,11 @@ describe('Test video blacklist API validators', function () { | |||
220 | const basePath = '/api/v1/videos/blacklist/' | 220 | const basePath = '/api/v1/videos/blacklist/' |
221 | 221 | ||
222 | it('Should fail with a non authenticated user', async function () { | 222 | it('Should fail with a non authenticated user', async function () { |
223 | await getBlacklistedVideosList(servers[0].url, 'fake token', 401) | 223 | await getBlacklistedVideosList({ url: servers[0].url, token: 'fake token', specialStatus: 401 }) |
224 | }) | 224 | }) |
225 | 225 | ||
226 | it('Should fail with a non admin user', async function () { | 226 | it('Should fail with a non admin user', async function () { |
227 | await getBlacklistedVideosList(servers[0].url, userAccessToken2, 403) | 227 | await getBlacklistedVideosList({ url: servers[0].url, token: userAccessToken2, specialStatus: 403 }) |
228 | }) | 228 | }) |
229 | 229 | ||
230 | it('Should fail with a bad start pagination', async function () { | 230 | it('Should fail with a bad start pagination', async function () { |
@@ -238,14 +238,17 @@ describe('Test video blacklist API validators', function () { | |||
238 | it('Should fail with an incorrect sort', async function () { | 238 | it('Should fail with an incorrect sort', async function () { |
239 | await checkBadSortPagination(servers[0].url, basePath, servers[0].accessToken) | 239 | await checkBadSortPagination(servers[0].url, basePath, servers[0].accessToken) |
240 | }) | 240 | }) |
241 | |||
242 | it('Should fail with an invalid type', async function () { | ||
243 | await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, type: 0, specialStatus: 400 }) | ||
244 | }) | ||
245 | |||
246 | it('Should succeed with the correct parameters', async function () { | ||
247 | await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken, type: VideoBlacklistType.MANUAL }) | ||
248 | }) | ||
241 | }) | 249 | }) |
242 | 250 | ||
243 | after(async function () { | 251 | after(async function () { |
244 | killallServers(servers) | 252 | await cleanupTests(servers) |
245 | |||
246 | // Keep the logs if the test failed | ||
247 | if (this['ok']) { | ||
248 | await flushTests() | ||
249 | } | ||
250 | }) | 253 | }) |
251 | }) | 254 | }) |
diff --git a/server/tests/api/check-params/video-captions.ts b/server/tests/api/check-params/video-captions.ts index e4d36fd4f..4a373d43d 100644 --- a/server/tests/api/check-params/video-captions.ts +++ b/server/tests/api/check-params/video-captions.ts | |||
@@ -2,20 +2,19 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import { | 4 | import { |
5 | cleanupTests, | ||
5 | createUser, | 6 | createUser, |
6 | flushTests, | 7 | flushAndRunServer, |
7 | killallServers, | ||
8 | makeDeleteRequest, | 8 | makeDeleteRequest, |
9 | makeGetRequest, | 9 | makeGetRequest, |
10 | makeUploadRequest, | 10 | makeUploadRequest, |
11 | runServer, | ||
12 | ServerInfo, | 11 | ServerInfo, |
13 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
14 | uploadVideo, | 13 | uploadVideo, |
15 | userLogin | 14 | userLogin |
16 | } from '../../../../shared/utils' | 15 | } from '../../../../shared/extra-utils' |
17 | import { join } from 'path' | 16 | import { join } from 'path' |
18 | import { createVideoCaption } from '../../../../shared/utils/videos/video-captions' | 17 | import { createVideoCaption } from '../../../../shared/extra-utils/videos/video-captions' |
19 | 18 | ||
20 | describe('Test video captions API validator', function () { | 19 | describe('Test video captions API validator', function () { |
21 | const path = '/api/v1/videos/' | 20 | const path = '/api/v1/videos/' |
@@ -29,9 +28,7 @@ describe('Test video captions API validator', function () { | |||
29 | before(async function () { | 28 | before(async function () { |
30 | this.timeout(30000) | 29 | this.timeout(30000) |
31 | 30 | ||
32 | await flushTests() | 31 | server = await flushAndRunServer(1) |
33 | |||
34 | server = await runServer(1) | ||
35 | 32 | ||
36 | await setAccessTokensToServers([ server ]) | 33 | await setAccessTokensToServers([ server ]) |
37 | 34 | ||
@@ -45,7 +42,7 @@ describe('Test video captions API validator', function () { | |||
45 | username: 'user1', | 42 | username: 'user1', |
46 | password: 'my super password' | 43 | password: 'my super password' |
47 | } | 44 | } |
48 | await createUser(server.url, server.accessToken, user.username, user.password) | 45 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
49 | userAccessToken = await userLogin(server, user) | 46 | userAccessToken = await userLogin(server, user) |
50 | } | 47 | } |
51 | }) | 48 | }) |
@@ -272,11 +269,6 @@ describe('Test video captions API validator', function () { | |||
272 | }) | 269 | }) |
273 | 270 | ||
274 | after(async function () { | 271 | after(async function () { |
275 | killallServers([ server ]) | 272 | await cleanupTests([ server ]) |
276 | |||
277 | // Keep the logs if the test failed | ||
278 | if (this['ok']) { | ||
279 | await flushTests() | ||
280 | } | ||
281 | }) | 273 | }) |
282 | }) | 274 | }) |
diff --git a/server/tests/api/check-params/video-channels.ts b/server/tests/api/check-params/video-channels.ts index 14e4deaf7..65bc20613 100644 --- a/server/tests/api/check-params/video-channels.ts +++ b/server/tests/api/check-params/video-channels.ts | |||
@@ -4,29 +4,25 @@ import * as chai from 'chai' | |||
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
5 | import 'mocha' | 5 | import 'mocha' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | createUser, | 8 | createUser, |
8 | deleteVideoChannel, | 9 | deleteVideoChannel, |
9 | flushTests, | 10 | flushAndRunServer, |
10 | getAccountVideoChannelsList, | 11 | getAccountVideoChannelsList, |
11 | getMyUserInformation, | ||
12 | getVideoChannelsList, | ||
13 | immutableAssign, | 12 | immutableAssign, |
14 | killallServers, | ||
15 | makeGetRequest, | 13 | makeGetRequest, |
16 | makePostBodyRequest, | 14 | makePostBodyRequest, |
17 | makePutBodyRequest, | 15 | makePutBodyRequest, |
18 | makeUploadRequest, | 16 | makeUploadRequest, |
19 | runServer, | ||
20 | ServerInfo, | 17 | ServerInfo, |
21 | setAccessTokensToServers, | 18 | setAccessTokensToServers, |
22 | userLogin | 19 | userLogin |
23 | } from '../../../../shared/utils' | 20 | } from '../../../../shared/extra-utils' |
24 | import { | 21 | import { |
25 | checkBadCountPagination, | 22 | checkBadCountPagination, |
26 | checkBadSortPagination, | 23 | checkBadSortPagination, |
27 | checkBadStartPagination | 24 | checkBadStartPagination |
28 | } from '../../../../shared/utils/requests/check-api-params' | 25 | } from '../../../../shared/extra-utils/requests/check-api-params' |
29 | import { User } from '../../../../shared/models/users' | ||
30 | import { join } from 'path' | 26 | import { join } from 'path' |
31 | 27 | ||
32 | const expect = chai.expect | 28 | const expect = chai.expect |
@@ -41,9 +37,7 @@ describe('Test video channels API validator', function () { | |||
41 | before(async function () { | 37 | before(async function () { |
42 | this.timeout(30000) | 38 | this.timeout(30000) |
43 | 39 | ||
44 | await flushTests() | 40 | server = await flushAndRunServer(1) |
45 | |||
46 | server = await runServer(1) | ||
47 | 41 | ||
48 | await setAccessTokensToServers([ server ]) | 42 | await setAccessTokensToServers([ server ]) |
49 | 43 | ||
@@ -53,7 +47,7 @@ describe('Test video channels API validator', function () { | |||
53 | } | 47 | } |
54 | 48 | ||
55 | { | 49 | { |
56 | await createUser(server.url, server.accessToken, user.username, user.password) | 50 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
57 | accessTokenUser = await userLogin(server, user) | 51 | accessTokenUser = await userLogin(server, user) |
58 | } | 52 | } |
59 | }) | 53 | }) |
@@ -313,11 +307,6 @@ describe('Test video channels API validator', function () { | |||
313 | }) | 307 | }) |
314 | 308 | ||
315 | after(async function () { | 309 | after(async function () { |
316 | killallServers([ server ]) | 310 | await cleanupTests([ server ]) |
317 | |||
318 | // Keep the logs if the test failed | ||
319 | if (this['ok']) { | ||
320 | await flushTests() | ||
321 | } | ||
322 | }) | 311 | }) |
323 | }) | 312 | }) |
diff --git a/server/tests/api/check-params/video-comments.ts b/server/tests/api/check-params/video-comments.ts index 5981780ed..5cf90bacc 100644 --- a/server/tests/api/check-params/video-comments.ts +++ b/server/tests/api/check-params/video-comments.ts | |||
@@ -3,16 +3,23 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePostBodyRequest, runServer, ServerInfo, setAccessTokensToServers, | 8 | flushAndRunServer, |
8 | uploadVideo, userLogin | 9 | makeDeleteRequest, |
9 | } from '../../../../shared/utils' | 10 | makeGetRequest, |
11 | makePostBodyRequest, | ||
12 | ServerInfo, | ||
13 | setAccessTokensToServers, | ||
14 | uploadVideo, | ||
15 | userLogin | ||
16 | } from '../../../../shared/extra-utils' | ||
10 | import { | 17 | import { |
11 | checkBadCountPagination, | 18 | checkBadCountPagination, |
12 | checkBadSortPagination, | 19 | checkBadSortPagination, |
13 | checkBadStartPagination | 20 | checkBadStartPagination |
14 | } from '../../../../shared/utils/requests/check-api-params' | 21 | } from '../../../../shared/extra-utils/requests/check-api-params' |
15 | import { addVideoCommentThread } from '../../../../shared/utils/videos/video-comments' | 22 | import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' |
16 | 23 | ||
17 | const expect = chai.expect | 24 | const expect = chai.expect |
18 | 25 | ||
@@ -29,9 +36,7 @@ describe('Test video comments API validator', function () { | |||
29 | before(async function () { | 36 | before(async function () { |
30 | this.timeout(30000) | 37 | this.timeout(30000) |
31 | 38 | ||
32 | await flushTests() | 39 | server = await flushAndRunServer(1) |
33 | |||
34 | server = await runServer(1) | ||
35 | 40 | ||
36 | await setAccessTokensToServers([ server ]) | 41 | await setAccessTokensToServers([ server ]) |
37 | 42 | ||
@@ -52,7 +57,7 @@ describe('Test video comments API validator', function () { | |||
52 | username: 'user1', | 57 | username: 'user1', |
53 | password: 'my super password' | 58 | password: 'my super password' |
54 | } | 59 | } |
55 | await createUser(server.url, server.accessToken, user.username, user.password) | 60 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
56 | userAccessToken = await userLogin(server, user) | 61 | userAccessToken = await userLogin(server, user) |
57 | } | 62 | } |
58 | }) | 63 | }) |
@@ -254,11 +259,6 @@ describe('Test video comments API validator', function () { | |||
254 | }) | 259 | }) |
255 | 260 | ||
256 | after(async function () { | 261 | after(async function () { |
257 | killallServers([ server ]) | 262 | await cleanupTests([ server ]) |
258 | |||
259 | // Keep the logs if the test failed | ||
260 | if (this['ok']) { | ||
261 | await flushTests() | ||
262 | } | ||
263 | }) | 263 | }) |
264 | }) | 264 | }) |
diff --git a/server/tests/api/check-params/video-imports.ts b/server/tests/api/check-params/video-imports.ts index 7bf187007..8ff115e7b 100644 --- a/server/tests/api/check-params/video-imports.ts +++ b/server/tests/api/check-params/video-imports.ts | |||
@@ -5,26 +5,25 @@ import 'mocha' | |||
5 | import { join } from 'path' | 5 | import { join } from 'path' |
6 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' | 6 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' |
7 | import { | 7 | import { |
8 | cleanupTests, | ||
8 | createUser, | 9 | createUser, |
9 | flushTests, | 10 | flushAndRunServer, |
10 | getMyUserInformation, | 11 | getMyUserInformation, |
11 | immutableAssign, | 12 | immutableAssign, |
12 | killallServers, | ||
13 | makeGetRequest, | 13 | makeGetRequest, |
14 | makePostBodyRequest, | 14 | makePostBodyRequest, |
15 | makeUploadRequest, | 15 | makeUploadRequest, |
16 | runServer, | ||
17 | ServerInfo, | 16 | ServerInfo, |
18 | setAccessTokensToServers, | 17 | setAccessTokensToServers, |
19 | updateCustomSubConfig, | 18 | updateCustomSubConfig, |
20 | userLogin | 19 | userLogin |
21 | } from '../../../../shared/utils' | 20 | } from '../../../../shared/extra-utils' |
22 | import { | 21 | import { |
23 | checkBadCountPagination, | 22 | checkBadCountPagination, |
24 | checkBadSortPagination, | 23 | checkBadSortPagination, |
25 | checkBadStartPagination | 24 | checkBadStartPagination |
26 | } from '../../../../shared/utils/requests/check-api-params' | 25 | } from '../../../../shared/extra-utils/requests/check-api-params' |
27 | import { getMagnetURI, getYoutubeVideoUrl } from '../../../../shared/utils/videos/video-imports' | 26 | import { getMagnetURI, getYoutubeVideoUrl } from '../../../../shared/extra-utils/videos/video-imports' |
28 | 27 | ||
29 | describe('Test video imports API validator', function () { | 28 | describe('Test video imports API validator', function () { |
30 | const path = '/api/v1/videos/imports' | 29 | const path = '/api/v1/videos/imports' |
@@ -38,15 +37,13 @@ describe('Test video imports API validator', function () { | |||
38 | before(async function () { | 37 | before(async function () { |
39 | this.timeout(30000) | 38 | this.timeout(30000) |
40 | 39 | ||
41 | await flushTests() | 40 | server = await flushAndRunServer(1) |
42 | |||
43 | server = await runServer(1) | ||
44 | 41 | ||
45 | await setAccessTokensToServers([ server ]) | 42 | await setAccessTokensToServers([ server ]) |
46 | 43 | ||
47 | const username = 'user1' | 44 | const username = 'user1' |
48 | const password = 'my super password' | 45 | const password = 'my super password' |
49 | await createUser(server.url, server.accessToken, username, password) | 46 | await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) |
50 | userAccessToken = await userLogin(server, { username, password }) | 47 | userAccessToken = await userLogin(server, { username, password }) |
51 | 48 | ||
52 | { | 49 | { |
@@ -88,6 +85,7 @@ describe('Test video imports API validator', function () { | |||
88 | language: 'pt', | 85 | language: 'pt', |
89 | nsfw: false, | 86 | nsfw: false, |
90 | commentsEnabled: true, | 87 | commentsEnabled: true, |
88 | downloadEnabled: true, | ||
91 | waitTranscoding: true, | 89 | waitTranscoding: true, |
92 | description: 'my super description', | 90 | description: 'my super description', |
93 | support: 'my super support text', | 91 | support: 'my super support text', |
@@ -166,7 +164,7 @@ describe('Test video imports API validator', function () { | |||
166 | username: 'fake', | 164 | username: 'fake', |
167 | password: 'fake_password' | 165 | password: 'fake_password' |
168 | } | 166 | } |
169 | await createUser(server.url, server.accessToken, user.username, user.password) | 167 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
170 | 168 | ||
171 | const accessTokenUser = await userLogin(server, user) | 169 | const accessTokenUser = await userLogin(server, user) |
172 | const res = await getMyUserInformation(server.url, accessTokenUser) | 170 | const res = await getMyUserInformation(server.url, accessTokenUser) |
@@ -313,11 +311,6 @@ describe('Test video imports API validator', function () { | |||
313 | }) | 311 | }) |
314 | 312 | ||
315 | after(async function () { | 313 | after(async function () { |
316 | killallServers([ server ]) | 314 | await cleanupTests([ server ]) |
317 | |||
318 | // Keep the logs if the test failed | ||
319 | if (this['ok']) { | ||
320 | await flushTests() | ||
321 | } | ||
322 | }) | 315 | }) |
323 | }) | 316 | }) |
diff --git a/server/tests/api/check-params/video-playlists.ts b/server/tests/api/check-params/video-playlists.ts new file mode 100644 index 000000000..b7b94c035 --- /dev/null +++ b/server/tests/api/check-params/video-playlists.ts | |||
@@ -0,0 +1,674 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { | ||
5 | addVideoInPlaylist, | ||
6 | cleanupTests, | ||
7 | createVideoPlaylist, | ||
8 | deleteVideoPlaylist, | ||
9 | flushAndRunServer, | ||
10 | generateUserAccessToken, | ||
11 | getAccountPlaylistsListWithToken, | ||
12 | getVideoPlaylist, | ||
13 | immutableAssign, | ||
14 | makeGetRequest, | ||
15 | removeVideoFromPlaylist, | ||
16 | reorderVideosPlaylist, | ||
17 | ServerInfo, | ||
18 | setAccessTokensToServers, | ||
19 | setDefaultVideoChannel, | ||
20 | updateVideoPlaylist, | ||
21 | updateVideoPlaylistElement, | ||
22 | uploadVideoAndGetId | ||
23 | } from '../../../../shared/extra-utils' | ||
24 | import { | ||
25 | checkBadCountPagination, | ||
26 | checkBadSortPagination, | ||
27 | checkBadStartPagination | ||
28 | } from '../../../../shared/extra-utils/requests/check-api-params' | ||
29 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
30 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' | ||
31 | |||
32 | describe('Test video playlists API validator', function () { | ||
33 | let server: ServerInfo | ||
34 | let userAccessToken: string | ||
35 | let playlistUUID: string | ||
36 | let privatePlaylistUUID: string | ||
37 | let watchLaterPlaylistId: number | ||
38 | let videoId: number | ||
39 | let videoId2: number | ||
40 | |||
41 | // --------------------------------------------------------------- | ||
42 | |||
43 | before(async function () { | ||
44 | this.timeout(30000) | ||
45 | |||
46 | server = await flushAndRunServer(1) | ||
47 | |||
48 | await setAccessTokensToServers([ server ]) | ||
49 | await setDefaultVideoChannel([ server ]) | ||
50 | |||
51 | userAccessToken = await generateUserAccessToken(server, 'user1') | ||
52 | videoId = (await uploadVideoAndGetId({ server, videoName: 'video 1' })).id | ||
53 | videoId2 = (await uploadVideoAndGetId({ server, videoName: 'video 2' })).id | ||
54 | |||
55 | { | ||
56 | const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root',0, 5, VideoPlaylistType.WATCH_LATER) | ||
57 | watchLaterPlaylistId = res.body.data[0].id | ||
58 | } | ||
59 | |||
60 | { | ||
61 | const res = await createVideoPlaylist({ | ||
62 | url: server.url, | ||
63 | token: server.accessToken, | ||
64 | playlistAttrs: { | ||
65 | displayName: 'super playlist', | ||
66 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
67 | videoChannelId: server.videoChannel.id | ||
68 | } | ||
69 | }) | ||
70 | playlistUUID = res.body.videoPlaylist.uuid | ||
71 | } | ||
72 | |||
73 | { | ||
74 | const res = await createVideoPlaylist({ | ||
75 | url: server.url, | ||
76 | token: server.accessToken, | ||
77 | playlistAttrs: { | ||
78 | displayName: 'private', | ||
79 | privacy: VideoPlaylistPrivacy.PRIVATE | ||
80 | } | ||
81 | }) | ||
82 | privatePlaylistUUID = res.body.videoPlaylist.uuid | ||
83 | } | ||
84 | }) | ||
85 | |||
86 | describe('When listing playlists', function () { | ||
87 | const globalPath = '/api/v1/video-playlists' | ||
88 | const accountPath = '/api/v1/accounts/root/video-playlists' | ||
89 | const videoChannelPath = '/api/v1/video-channels/root_channel/video-playlists' | ||
90 | |||
91 | it('Should fail with a bad start pagination', async function () { | ||
92 | await checkBadStartPagination(server.url, globalPath, server.accessToken) | ||
93 | await checkBadStartPagination(server.url, accountPath, server.accessToken) | ||
94 | await checkBadStartPagination(server.url, videoChannelPath, server.accessToken) | ||
95 | }) | ||
96 | |||
97 | it('Should fail with a bad count pagination', async function () { | ||
98 | await checkBadCountPagination(server.url, globalPath, server.accessToken) | ||
99 | await checkBadCountPagination(server.url, accountPath, server.accessToken) | ||
100 | await checkBadCountPagination(server.url, videoChannelPath, server.accessToken) | ||
101 | }) | ||
102 | |||
103 | it('Should fail with an incorrect sort', async function () { | ||
104 | await checkBadSortPagination(server.url, globalPath, server.accessToken) | ||
105 | await checkBadSortPagination(server.url, accountPath, server.accessToken) | ||
106 | await checkBadSortPagination(server.url, videoChannelPath, server.accessToken) | ||
107 | }) | ||
108 | |||
109 | it('Should fail with a bad playlist type', async function () { | ||
110 | await makeGetRequest({ url: server.url, path: globalPath, query: { playlistType: 3 } }) | ||
111 | await makeGetRequest({ url: server.url, path: accountPath, query: { playlistType: 3 } }) | ||
112 | await makeGetRequest({ url: server.url, path: videoChannelPath, query: { playlistType: 3 } }) | ||
113 | }) | ||
114 | |||
115 | it('Should fail with a bad account parameter', async function () { | ||
116 | const accountPath = '/api/v1/accounts/root2/video-playlists' | ||
117 | |||
118 | await makeGetRequest({ url: server.url, path: accountPath, statusCodeExpected: 404, token: server.accessToken }) | ||
119 | }) | ||
120 | |||
121 | it('Should fail with a bad video channel parameter', async function () { | ||
122 | const accountPath = '/api/v1/video-channels/bad_channel/video-playlists' | ||
123 | |||
124 | await makeGetRequest({ url: server.url, path: accountPath, statusCodeExpected: 404, token: server.accessToken }) | ||
125 | }) | ||
126 | |||
127 | it('Should success with the correct parameters', async function () { | ||
128 | await makeGetRequest({ url: server.url, path: globalPath, statusCodeExpected: 200, token: server.accessToken }) | ||
129 | await makeGetRequest({ url: server.url, path: accountPath, statusCodeExpected: 200, token: server.accessToken }) | ||
130 | await makeGetRequest({ url: server.url, path: videoChannelPath, statusCodeExpected: 200, token: server.accessToken }) | ||
131 | }) | ||
132 | }) | ||
133 | |||
134 | describe('When listing videos of a playlist', function () { | ||
135 | const path = '/api/v1/video-playlists' | ||
136 | |||
137 | it('Should fail with a bad start pagination', async function () { | ||
138 | await checkBadStartPagination(server.url, path, server.accessToken) | ||
139 | }) | ||
140 | |||
141 | it('Should fail with a bad count pagination', async function () { | ||
142 | await checkBadCountPagination(server.url, path, server.accessToken) | ||
143 | }) | ||
144 | |||
145 | it('Should fail with a bad filter', async function () { | ||
146 | await checkBadSortPagination(server.url, path, server.accessToken) | ||
147 | }) | ||
148 | }) | ||
149 | |||
150 | describe('When getting a video playlist', function () { | ||
151 | it('Should fail with a bad id or uuid', async function () { | ||
152 | await getVideoPlaylist(server.url, 'toto', 400) | ||
153 | }) | ||
154 | |||
155 | it('Should fail with an unknown playlist', async function () { | ||
156 | await getVideoPlaylist(server.url, 42, 404) | ||
157 | }) | ||
158 | |||
159 | it('Should fail to get an unlisted playlist with the number id', async function () { | ||
160 | const res = await createVideoPlaylist({ | ||
161 | url: server.url, | ||
162 | token: server.accessToken, | ||
163 | playlistAttrs: { | ||
164 | displayName: 'super playlist', | ||
165 | privacy: VideoPlaylistPrivacy.UNLISTED | ||
166 | } | ||
167 | }) | ||
168 | const playlist = res.body.videoPlaylist | ||
169 | |||
170 | await getVideoPlaylist(server.url, playlist.id, 404) | ||
171 | await getVideoPlaylist(server.url, playlist.uuid, 200) | ||
172 | }) | ||
173 | |||
174 | it('Should succeed with the correct params', async function () { | ||
175 | await getVideoPlaylist(server.url, playlistUUID, 200) | ||
176 | }) | ||
177 | }) | ||
178 | |||
179 | describe('When creating/updating a video playlist', function () { | ||
180 | const getBase = (playlistAttrs: any = {}, wrapper: any = {}) => { | ||
181 | return Object.assign({ | ||
182 | expectedStatus: 400, | ||
183 | url: server.url, | ||
184 | token: server.accessToken, | ||
185 | playlistAttrs: Object.assign({ | ||
186 | displayName: 'display name', | ||
187 | privacy: VideoPlaylistPrivacy.UNLISTED, | ||
188 | thumbnailfile: 'thumbnail.jpg', | ||
189 | videoChannelId: server.videoChannel.id | ||
190 | }, playlistAttrs) | ||
191 | }, wrapper) | ||
192 | } | ||
193 | const getUpdate = (params: any, playlistId: number | string) => { | ||
194 | return immutableAssign(params, { playlistId: playlistId }) | ||
195 | } | ||
196 | |||
197 | it('Should fail with an unauthenticated user', async function () { | ||
198 | const params = getBase({}, { token: null, expectedStatus: 401 }) | ||
199 | |||
200 | await createVideoPlaylist(params) | ||
201 | await updateVideoPlaylist(getUpdate(params, playlistUUID)) | ||
202 | }) | ||
203 | |||
204 | it('Should fail without displayName', async function () { | ||
205 | const params = getBase({ displayName: undefined }) | ||
206 | |||
207 | await createVideoPlaylist(params) | ||
208 | await updateVideoPlaylist(getUpdate(params, playlistUUID)) | ||
209 | }) | ||
210 | |||
211 | it('Should fail with an incorrect display name', async function () { | ||
212 | const params = getBase({ displayName: 's'.repeat(300) }) | ||
213 | |||
214 | await createVideoPlaylist(params) | ||
215 | await updateVideoPlaylist(getUpdate(params, playlistUUID)) | ||
216 | }) | ||
217 | |||
218 | it('Should fail with an incorrect description', async function () { | ||
219 | const params = getBase({ description: 't' }) | ||
220 | |||
221 | await createVideoPlaylist(params) | ||
222 | await updateVideoPlaylist(getUpdate(params, playlistUUID)) | ||
223 | }) | ||
224 | |||
225 | it('Should fail with an incorrect privacy', async function () { | ||
226 | const params = getBase({ privacy: 45 }) | ||
227 | |||
228 | await createVideoPlaylist(params) | ||
229 | await updateVideoPlaylist(getUpdate(params, playlistUUID)) | ||
230 | }) | ||
231 | |||
232 | it('Should fail with an unknown video channel id', async function () { | ||
233 | const params = getBase({ videoChannelId: 42 }, { expectedStatus: 404 }) | ||
234 | |||
235 | await createVideoPlaylist(params) | ||
236 | await updateVideoPlaylist(getUpdate(params, playlistUUID)) | ||
237 | }) | ||
238 | |||
239 | it('Should fail with an incorrect thumbnail file', async function () { | ||
240 | const params = getBase({ thumbnailfile: 'avatar.png' }) | ||
241 | |||
242 | await createVideoPlaylist(params) | ||
243 | await updateVideoPlaylist(getUpdate(params, playlistUUID)) | ||
244 | }) | ||
245 | |||
246 | it('Should fail to set "public" a playlist not assigned to a channel', async function () { | ||
247 | const params = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: undefined }) | ||
248 | const params2 = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC, videoChannelId: 'null' }) | ||
249 | const params3 = getBase({ privacy: undefined, videoChannelId: 'null' }) | ||
250 | |||
251 | await createVideoPlaylist(params) | ||
252 | await createVideoPlaylist(params2) | ||
253 | await updateVideoPlaylist(getUpdate(params, privatePlaylistUUID)) | ||
254 | await updateVideoPlaylist(getUpdate(params2, playlistUUID)) | ||
255 | await updateVideoPlaylist(getUpdate(params3, playlistUUID)) | ||
256 | }) | ||
257 | |||
258 | it('Should fail with an unknown playlist to update', async function () { | ||
259 | await updateVideoPlaylist(getUpdate( | ||
260 | getBase({}, { expectedStatus: 404 }), | ||
261 | 42 | ||
262 | )) | ||
263 | }) | ||
264 | |||
265 | it('Should fail to update a playlist of another user', async function () { | ||
266 | await updateVideoPlaylist(getUpdate( | ||
267 | getBase({}, { token: userAccessToken, expectedStatus: 403 }), | ||
268 | playlistUUID | ||
269 | )) | ||
270 | }) | ||
271 | |||
272 | it('Should fail to update to private a public/unlisted playlist', async function () { | ||
273 | const params = getBase({ privacy: VideoPlaylistPrivacy.PUBLIC }, { expectedStatus: 200 }) | ||
274 | |||
275 | const res = await createVideoPlaylist(params) | ||
276 | const playlist = res.body.videoPlaylist | ||
277 | |||
278 | const paramsUpdate = getBase({ privacy: VideoPlaylistPrivacy.PRIVATE }, { expectedStatus: 400 }) | ||
279 | |||
280 | await updateVideoPlaylist(getUpdate(paramsUpdate, playlist.id)) | ||
281 | }) | ||
282 | |||
283 | it('Should fail to update the watch later playlist', async function () { | ||
284 | await updateVideoPlaylist(getUpdate( | ||
285 | getBase({}, { expectedStatus: 400 }), | ||
286 | watchLaterPlaylistId | ||
287 | )) | ||
288 | }) | ||
289 | |||
290 | it('Should succeed with the correct params', async function () { | ||
291 | { | ||
292 | const params = getBase({}, { expectedStatus: 200 }) | ||
293 | await createVideoPlaylist(params) | ||
294 | } | ||
295 | |||
296 | { | ||
297 | const params = getBase({}, { expectedStatus: 204 }) | ||
298 | await updateVideoPlaylist(getUpdate(params, playlistUUID)) | ||
299 | } | ||
300 | }) | ||
301 | }) | ||
302 | |||
303 | describe('When adding an element in a playlist', function () { | ||
304 | const getBase = (elementAttrs: any = {}, wrapper: any = {}) => { | ||
305 | return Object.assign({ | ||
306 | expectedStatus: 400, | ||
307 | url: server.url, | ||
308 | token: server.accessToken, | ||
309 | playlistId: playlistUUID, | ||
310 | elementAttrs: Object.assign({ | ||
311 | videoId: videoId, | ||
312 | startTimestamp: 2, | ||
313 | stopTimestamp: 3 | ||
314 | }, elementAttrs) | ||
315 | }, wrapper) | ||
316 | } | ||
317 | |||
318 | it('Should fail with an unauthenticated user', async function () { | ||
319 | const params = getBase({}, { token: null, expectedStatus: 401 }) | ||
320 | await addVideoInPlaylist(params) | ||
321 | }) | ||
322 | |||
323 | it('Should fail with the playlist of another user', async function () { | ||
324 | const params = getBase({}, { token: userAccessToken, expectedStatus: 403 }) | ||
325 | await addVideoInPlaylist(params) | ||
326 | }) | ||
327 | |||
328 | it('Should fail with an unknown or incorrect playlist id', async function () { | ||
329 | { | ||
330 | const params = getBase({}, { playlistId: 'toto' }) | ||
331 | await addVideoInPlaylist(params) | ||
332 | } | ||
333 | |||
334 | { | ||
335 | const params = getBase({}, { playlistId: 42, expectedStatus: 404 }) | ||
336 | await addVideoInPlaylist(params) | ||
337 | } | ||
338 | }) | ||
339 | |||
340 | it('Should fail with an unknown or incorrect video id', async function () { | ||
341 | const params = getBase({ videoId: 42 }, { expectedStatus: 404 }) | ||
342 | await addVideoInPlaylist(params) | ||
343 | }) | ||
344 | |||
345 | it('Should fail with a bad start/stop timestamp', async function () { | ||
346 | { | ||
347 | const params = getBase({ startTimestamp: -42 }) | ||
348 | await addVideoInPlaylist(params) | ||
349 | } | ||
350 | |||
351 | { | ||
352 | const params = getBase({ stopTimestamp: 'toto' as any }) | ||
353 | await addVideoInPlaylist(params) | ||
354 | } | ||
355 | }) | ||
356 | |||
357 | it('Succeed with the correct params', async function () { | ||
358 | const params = getBase({}, { expectedStatus: 200 }) | ||
359 | await addVideoInPlaylist(params) | ||
360 | }) | ||
361 | |||
362 | it('Should fail if the video was already added in the playlist', async function () { | ||
363 | const params = getBase({}, { expectedStatus: 409 }) | ||
364 | await addVideoInPlaylist(params) | ||
365 | }) | ||
366 | }) | ||
367 | |||
368 | describe('When updating an element in a playlist', function () { | ||
369 | const getBase = (elementAttrs: any = {}, wrapper: any = {}) => { | ||
370 | return Object.assign({ | ||
371 | url: server.url, | ||
372 | token: server.accessToken, | ||
373 | elementAttrs: Object.assign({ | ||
374 | startTimestamp: 1, | ||
375 | stopTimestamp: 2 | ||
376 | }, elementAttrs), | ||
377 | videoId: videoId, | ||
378 | playlistId: playlistUUID, | ||
379 | expectedStatus: 400 | ||
380 | }, wrapper) | ||
381 | } | ||
382 | |||
383 | it('Should fail with an unauthenticated user', async function () { | ||
384 | const params = getBase({}, { token: null, expectedStatus: 401 }) | ||
385 | await updateVideoPlaylistElement(params) | ||
386 | }) | ||
387 | |||
388 | it('Should fail with the playlist of another user', async function () { | ||
389 | const params = getBase({}, { token: userAccessToken, expectedStatus: 403 }) | ||
390 | await updateVideoPlaylistElement(params) | ||
391 | }) | ||
392 | |||
393 | it('Should fail with an unknown or incorrect playlist id', async function () { | ||
394 | { | ||
395 | const params = getBase({}, { playlistId: 'toto' }) | ||
396 | await updateVideoPlaylistElement(params) | ||
397 | } | ||
398 | |||
399 | { | ||
400 | const params = getBase({}, { playlistId: 42, expectedStatus: 404 }) | ||
401 | await updateVideoPlaylistElement(params) | ||
402 | } | ||
403 | }) | ||
404 | |||
405 | it('Should fail with an unknown or incorrect video id', async function () { | ||
406 | { | ||
407 | const params = getBase({}, { videoId: 'toto' }) | ||
408 | await updateVideoPlaylistElement(params) | ||
409 | } | ||
410 | |||
411 | { | ||
412 | const params = getBase({}, { videoId: 42, expectedStatus: 404 }) | ||
413 | await updateVideoPlaylistElement(params) | ||
414 | } | ||
415 | }) | ||
416 | |||
417 | it('Should fail with a bad start/stop timestamp', async function () { | ||
418 | { | ||
419 | const params = getBase({ startTimestamp: 'toto' as any }) | ||
420 | await updateVideoPlaylistElement(params) | ||
421 | } | ||
422 | |||
423 | { | ||
424 | const params = getBase({ stopTimestamp: -42 }) | ||
425 | await updateVideoPlaylistElement(params) | ||
426 | } | ||
427 | }) | ||
428 | |||
429 | it('Should fail with an unknown element', async function () { | ||
430 | const params = getBase({}, { videoId: videoId2, expectedStatus: 404 }) | ||
431 | await updateVideoPlaylistElement(params) | ||
432 | }) | ||
433 | |||
434 | it('Succeed with the correct params', async function () { | ||
435 | const params = getBase({}, { expectedStatus: 204 }) | ||
436 | await updateVideoPlaylistElement(params) | ||
437 | }) | ||
438 | }) | ||
439 | |||
440 | describe('When reordering elements of a playlist', function () { | ||
441 | let videoId3: number | ||
442 | let videoId4: number | ||
443 | |||
444 | const getBase = (elementAttrs: any = {}, wrapper: any = {}) => { | ||
445 | return Object.assign({ | ||
446 | url: server.url, | ||
447 | token: server.accessToken, | ||
448 | playlistId: playlistUUID, | ||
449 | elementAttrs: Object.assign({ | ||
450 | startPosition: 1, | ||
451 | insertAfterPosition: 2, | ||
452 | reorderLength: 3 | ||
453 | }, elementAttrs), | ||
454 | expectedStatus: 400 | ||
455 | }, wrapper) | ||
456 | } | ||
457 | |||
458 | before(async function () { | ||
459 | videoId3 = (await uploadVideoAndGetId({ server, videoName: 'video 3' })).id | ||
460 | videoId4 = (await uploadVideoAndGetId({ server, videoName: 'video 4' })).id | ||
461 | |||
462 | for (let id of [ videoId3, videoId4 ]) { | ||
463 | await addVideoInPlaylist({ | ||
464 | url: server.url, | ||
465 | token: server.accessToken, | ||
466 | playlistId: playlistUUID, | ||
467 | elementAttrs: { videoId: id } | ||
468 | }) | ||
469 | } | ||
470 | }) | ||
471 | |||
472 | it('Should fail with an unauthenticated user', async function () { | ||
473 | const params = getBase({}, { token: null, expectedStatus: 401 }) | ||
474 | await reorderVideosPlaylist(params) | ||
475 | }) | ||
476 | |||
477 | it('Should fail with the playlist of another user', async function () { | ||
478 | const params = getBase({}, { token: userAccessToken, expectedStatus: 403 }) | ||
479 | await reorderVideosPlaylist(params) | ||
480 | }) | ||
481 | |||
482 | it('Should fail with an invalid playlist', async function () { | ||
483 | { | ||
484 | const params = getBase({}, { playlistId: 'toto' }) | ||
485 | await reorderVideosPlaylist(params) | ||
486 | } | ||
487 | |||
488 | { | ||
489 | const params = getBase({}, { playlistId: 42, expectedStatus: 404 }) | ||
490 | await reorderVideosPlaylist(params) | ||
491 | } | ||
492 | }) | ||
493 | |||
494 | it('Should fail with an invalid start position', async function () { | ||
495 | { | ||
496 | const params = getBase({ startPosition: -1 }) | ||
497 | await reorderVideosPlaylist(params) | ||
498 | } | ||
499 | |||
500 | { | ||
501 | const params = getBase({ startPosition: 'toto' as any }) | ||
502 | await reorderVideosPlaylist(params) | ||
503 | } | ||
504 | |||
505 | { | ||
506 | const params = getBase({ startPosition: 42 }) | ||
507 | await reorderVideosPlaylist(params) | ||
508 | } | ||
509 | }) | ||
510 | |||
511 | it('Should fail with an invalid insert after position', async function () { | ||
512 | { | ||
513 | const params = getBase({ insertAfterPosition: 'toto' as any }) | ||
514 | await reorderVideosPlaylist(params) | ||
515 | } | ||
516 | |||
517 | { | ||
518 | const params = getBase({ insertAfterPosition: -2 }) | ||
519 | await reorderVideosPlaylist(params) | ||
520 | } | ||
521 | |||
522 | { | ||
523 | const params = getBase({ insertAfterPosition: 42 }) | ||
524 | await reorderVideosPlaylist(params) | ||
525 | } | ||
526 | }) | ||
527 | |||
528 | it('Should fail with an invalid reorder length', async function () { | ||
529 | { | ||
530 | const params = getBase({ reorderLength: 'toto' as any }) | ||
531 | await reorderVideosPlaylist(params) | ||
532 | } | ||
533 | |||
534 | { | ||
535 | const params = getBase({ reorderLength: -2 }) | ||
536 | await reorderVideosPlaylist(params) | ||
537 | } | ||
538 | |||
539 | { | ||
540 | const params = getBase({ reorderLength: 42 }) | ||
541 | await reorderVideosPlaylist(params) | ||
542 | } | ||
543 | }) | ||
544 | |||
545 | it('Succeed with the correct params', async function () { | ||
546 | const params = getBase({}, { expectedStatus: 204 }) | ||
547 | await reorderVideosPlaylist(params) | ||
548 | }) | ||
549 | }) | ||
550 | |||
551 | describe('When checking exists in playlist endpoint', function () { | ||
552 | const path = '/api/v1/users/me/video-playlists/videos-exist' | ||
553 | |||
554 | it('Should fail with an unauthenticated user', async function () { | ||
555 | await makeGetRequest({ | ||
556 | url: server.url, | ||
557 | path, | ||
558 | query: { videoIds: [ 1, 2 ] }, | ||
559 | statusCodeExpected: 401 | ||
560 | }) | ||
561 | }) | ||
562 | |||
563 | it('Should fail with invalid video ids', async function () { | ||
564 | await makeGetRequest({ | ||
565 | url: server.url, | ||
566 | token: server.accessToken, | ||
567 | path, | ||
568 | query: { videoIds: 'toto' } | ||
569 | }) | ||
570 | |||
571 | await makeGetRequest({ | ||
572 | url: server.url, | ||
573 | token: server.accessToken, | ||
574 | path, | ||
575 | query: { videoIds: [ 'toto' ] } | ||
576 | }) | ||
577 | |||
578 | await makeGetRequest({ | ||
579 | url: server.url, | ||
580 | token: server.accessToken, | ||
581 | path, | ||
582 | query: { videoIds: [ 1, 'toto' ] } | ||
583 | }) | ||
584 | }) | ||
585 | |||
586 | it('Should succeed with the correct params', async function () { | ||
587 | await makeGetRequest({ | ||
588 | url: server.url, | ||
589 | token: server.accessToken, | ||
590 | path, | ||
591 | query: { videoIds: [ 1, 2 ] }, | ||
592 | statusCodeExpected: 200 | ||
593 | }) | ||
594 | }) | ||
595 | }) | ||
596 | |||
597 | describe('When deleting an element in a playlist', function () { | ||
598 | const getBase = (wrapper: any = {}) => { | ||
599 | return Object.assign({ | ||
600 | url: server.url, | ||
601 | token: server.accessToken, | ||
602 | videoId: videoId, | ||
603 | playlistId: playlistUUID, | ||
604 | expectedStatus: 400 | ||
605 | }, wrapper) | ||
606 | } | ||
607 | |||
608 | it('Should fail with an unauthenticated user', async function () { | ||
609 | const params = getBase({ token: null, expectedStatus: 401 }) | ||
610 | await removeVideoFromPlaylist(params) | ||
611 | }) | ||
612 | |||
613 | it('Should fail with the playlist of another user', async function () { | ||
614 | const params = getBase({ token: userAccessToken, expectedStatus: 403 }) | ||
615 | await removeVideoFromPlaylist(params) | ||
616 | }) | ||
617 | |||
618 | it('Should fail with an unknown or incorrect playlist id', async function () { | ||
619 | { | ||
620 | const params = getBase({ playlistId: 'toto' }) | ||
621 | await removeVideoFromPlaylist(params) | ||
622 | } | ||
623 | |||
624 | { | ||
625 | const params = getBase({ playlistId: 42, expectedStatus: 404 }) | ||
626 | await removeVideoFromPlaylist(params) | ||
627 | } | ||
628 | }) | ||
629 | |||
630 | it('Should fail with an unknown or incorrect video id', async function () { | ||
631 | { | ||
632 | const params = getBase({ videoId: 'toto' }) | ||
633 | await removeVideoFromPlaylist(params) | ||
634 | } | ||
635 | |||
636 | { | ||
637 | const params = getBase({ videoId: 42, expectedStatus: 404 }) | ||
638 | await removeVideoFromPlaylist(params) | ||
639 | } | ||
640 | }) | ||
641 | |||
642 | it('Should fail with an unknown element', async function () { | ||
643 | const params = getBase({ videoId: videoId2, expectedStatus: 404 }) | ||
644 | await removeVideoFromPlaylist(params) | ||
645 | }) | ||
646 | |||
647 | it('Succeed with the correct params', async function () { | ||
648 | const params = getBase({ expectedStatus: 204 }) | ||
649 | await removeVideoFromPlaylist(params) | ||
650 | }) | ||
651 | }) | ||
652 | |||
653 | describe('When deleting a playlist', function () { | ||
654 | it('Should fail with an unknown playlist', async function () { | ||
655 | await deleteVideoPlaylist(server.url, server.accessToken, 42, 404) | ||
656 | }) | ||
657 | |||
658 | it('Should fail with a playlist of another user', async function () { | ||
659 | await deleteVideoPlaylist(server.url, userAccessToken, playlistUUID, 403) | ||
660 | }) | ||
661 | |||
662 | it('Should fail with the watch later playlist', async function () { | ||
663 | await deleteVideoPlaylist(server.url, server.accessToken, watchLaterPlaylistId, 400) | ||
664 | }) | ||
665 | |||
666 | it('Should succeed with the correct params', async function () { | ||
667 | await deleteVideoPlaylist(server.url, server.accessToken, playlistUUID) | ||
668 | }) | ||
669 | }) | ||
670 | |||
671 | after(async function () { | ||
672 | await cleanupTests([ server ]) | ||
673 | }) | ||
674 | }) | ||
diff --git a/server/tests/api/check-params/videos-filter.ts b/server/tests/api/check-params/videos-filter.ts index e998c8a3d..babef8223 100644 --- a/server/tests/api/check-params/videos-filter.ts +++ b/server/tests/api/check-params/videos-filter.ts | |||
@@ -1,27 +1,27 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | 1 | /* tslint:disable:no-unused-expression */ |
2 | 2 | ||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | 3 | import 'mocha' |
5 | import { | 4 | import { |
5 | cleanupTests, | ||
6 | createUser, | 6 | createUser, |
7 | flushTests, | 7 | createVideoPlaylist, |
8 | killallServers, | 8 | flushAndRunServer, |
9 | makeGetRequest, | 9 | makeGetRequest, |
10 | runServer, | ||
11 | ServerInfo, | 10 | ServerInfo, |
12 | setAccessTokensToServers, | 11 | setAccessTokensToServers, |
12 | setDefaultVideoChannel, | ||
13 | userLogin | 13 | userLogin |
14 | } from '../../../../shared/utils' | 14 | } from '../../../../shared/extra-utils' |
15 | import { UserRole } from '../../../../shared/models/users' | 15 | import { UserRole } from '../../../../shared/models/users' |
16 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
16 | 17 | ||
17 | const expect = chai.expect | 18 | async function testEndpoints (server: ServerInfo, token: string, filter: string, playlistUUID: string, statusCodeExpected: number) { |
18 | |||
19 | async function testEndpoints (server: ServerInfo, token: string, filter: string, statusCodeExpected: number) { | ||
20 | const paths = [ | 19 | const paths = [ |
21 | '/api/v1/video-channels/root_channel/videos', | 20 | '/api/v1/video-channels/root_channel/videos', |
22 | '/api/v1/accounts/root/videos', | 21 | '/api/v1/accounts/root/videos', |
23 | '/api/v1/videos', | 22 | '/api/v1/videos', |
24 | '/api/v1/search/videos' | 23 | '/api/v1/search/videos', |
24 | '/api/v1/video-playlists/' + playlistUUID + '/videos' | ||
25 | ] | 25 | ] |
26 | 26 | ||
27 | for (const path of paths) { | 27 | for (const path of paths) { |
@@ -41,55 +41,68 @@ describe('Test videos filters', function () { | |||
41 | let server: ServerInfo | 41 | let server: ServerInfo |
42 | let userAccessToken: string | 42 | let userAccessToken: string |
43 | let moderatorAccessToken: string | 43 | let moderatorAccessToken: string |
44 | let playlistUUID: string | ||
44 | 45 | ||
45 | // --------------------------------------------------------------- | 46 | // --------------------------------------------------------------- |
46 | 47 | ||
47 | before(async function () { | 48 | before(async function () { |
48 | this.timeout(30000) | 49 | this.timeout(30000) |
49 | 50 | ||
50 | await flushTests() | 51 | server = await flushAndRunServer(1) |
51 | |||
52 | server = await runServer(1) | ||
53 | 52 | ||
54 | await setAccessTokensToServers([ server ]) | 53 | await setAccessTokensToServers([ server ]) |
54 | await setDefaultVideoChannel([ server ]) | ||
55 | 55 | ||
56 | const user = { username: 'user1', password: 'my super password' } | 56 | const user = { username: 'user1', password: 'my super password' } |
57 | await createUser(server.url, server.accessToken, user.username, user.password) | 57 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
58 | userAccessToken = await userLogin(server, user) | 58 | userAccessToken = await userLogin(server, user) |
59 | 59 | ||
60 | const moderator = { username: 'moderator', password: 'my super password' } | 60 | const moderator = { username: 'moderator', password: 'my super password' } |
61 | await createUser( | 61 | await createUser( |
62 | server.url, | 62 | { |
63 | server.accessToken, | 63 | url: server.url, |
64 | moderator.username, | 64 | accessToken: server.accessToken, |
65 | moderator.password, | 65 | username: moderator.username, |
66 | undefined, | 66 | password: moderator.password, |
67 | undefined, | 67 | videoQuota: undefined, |
68 | UserRole.MODERATOR | 68 | videoQuotaDaily: undefined, |
69 | role: UserRole.MODERATOR | ||
70 | } | ||
69 | ) | 71 | ) |
70 | moderatorAccessToken = await userLogin(server, moderator) | 72 | moderatorAccessToken = await userLogin(server, moderator) |
73 | |||
74 | const res = await createVideoPlaylist({ | ||
75 | url: server.url, | ||
76 | token: server.accessToken, | ||
77 | playlistAttrs: { | ||
78 | displayName: 'super playlist', | ||
79 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
80 | videoChannelId: server.videoChannel.id | ||
81 | } | ||
82 | }) | ||
83 | playlistUUID = res.body.videoPlaylist.uuid | ||
71 | }) | 84 | }) |
72 | 85 | ||
73 | describe('When setting a video filter', function () { | 86 | describe('When setting a video filter', function () { |
74 | 87 | ||
75 | it('Should fail with a bad filter', async function () { | 88 | it('Should fail with a bad filter', async function () { |
76 | await testEndpoints(server, server.accessToken, 'bad-filter', 400) | 89 | await testEndpoints(server, server.accessToken, 'bad-filter', playlistUUID, 400) |
77 | }) | 90 | }) |
78 | 91 | ||
79 | it('Should succeed with a good filter', async function () { | 92 | it('Should succeed with a good filter', async function () { |
80 | await testEndpoints(server, server.accessToken,'local', 200) | 93 | await testEndpoints(server, server.accessToken,'local', playlistUUID, 200) |
81 | }) | 94 | }) |
82 | 95 | ||
83 | it('Should fail to list all-local with a simple user', async function () { | 96 | it('Should fail to list all-local with a simple user', async function () { |
84 | await testEndpoints(server, userAccessToken, 'all-local', 401) | 97 | await testEndpoints(server, userAccessToken, 'all-local', playlistUUID, 401) |
85 | }) | 98 | }) |
86 | 99 | ||
87 | it('Should succeed to list all-local with a moderator', async function () { | 100 | it('Should succeed to list all-local with a moderator', async function () { |
88 | await testEndpoints(server, moderatorAccessToken, 'all-local', 200) | 101 | await testEndpoints(server, moderatorAccessToken, 'all-local', playlistUUID, 200) |
89 | }) | 102 | }) |
90 | 103 | ||
91 | it('Should succeed to list all-local with an admin', async function () { | 104 | it('Should succeed to list all-local with an admin', async function () { |
92 | await testEndpoints(server, server.accessToken, 'all-local', 200) | 105 | await testEndpoints(server, server.accessToken, 'all-local', playlistUUID, 200) |
93 | }) | 106 | }) |
94 | 107 | ||
95 | // Because we cannot authenticate the user on the RSS endpoint | 108 | // Because we cannot authenticate the user on the RSS endpoint |
@@ -104,7 +117,7 @@ describe('Test videos filters', function () { | |||
104 | }) | 117 | }) |
105 | }) | 118 | }) |
106 | 119 | ||
107 | it('Should succed on the feeds endpoint with the local filter', async function () { | 120 | it('Should succeed on the feeds endpoint with the local filter', async function () { |
108 | await makeGetRequest({ | 121 | await makeGetRequest({ |
109 | url: server.url, | 122 | url: server.url, |
110 | path: '/feeds/videos.json', | 123 | path: '/feeds/videos.json', |
@@ -117,11 +130,6 @@ describe('Test videos filters', function () { | |||
117 | }) | 130 | }) |
118 | 131 | ||
119 | after(async function () { | 132 | after(async function () { |
120 | killallServers([ server ]) | 133 | await cleanupTests([ server ]) |
121 | |||
122 | // Keep the logs if the test failed | ||
123 | if (this['ok']) { | ||
124 | await flushTests() | ||
125 | } | ||
126 | }) | 134 | }) |
127 | }) | 135 | }) |
diff --git a/server/tests/api/check-params/videos-history.ts b/server/tests/api/check-params/videos-history.ts index 8c079a956..3739e3fad 100644 --- a/server/tests/api/check-params/videos-history.ts +++ b/server/tests/api/check-params/videos-history.ts | |||
@@ -5,16 +5,15 @@ import 'mocha' | |||
5 | import { | 5 | import { |
6 | checkBadCountPagination, | 6 | checkBadCountPagination, |
7 | checkBadStartPagination, | 7 | checkBadStartPagination, |
8 | flushTests, | 8 | cleanupTests, |
9 | killallServers, | 9 | flushAndRunServer, |
10 | makeGetRequest, | 10 | makeGetRequest, |
11 | makePostBodyRequest, | 11 | makePostBodyRequest, |
12 | makePutBodyRequest, | 12 | makePutBodyRequest, |
13 | runServer, | ||
14 | ServerInfo, | 13 | ServerInfo, |
15 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
16 | uploadVideo | 15 | uploadVideo |
17 | } from '../../../../shared/utils' | 16 | } from '../../../../shared/extra-utils' |
18 | 17 | ||
19 | const expect = chai.expect | 18 | const expect = chai.expect |
20 | 19 | ||
@@ -29,9 +28,7 @@ describe('Test videos history API validator', function () { | |||
29 | before(async function () { | 28 | before(async function () { |
30 | this.timeout(30000) | 29 | this.timeout(30000) |
31 | 30 | ||
32 | await flushTests() | 31 | server = await flushAndRunServer(1) |
33 | |||
34 | server = await runServer(1) | ||
35 | 32 | ||
36 | await setAccessTokensToServers([ server ]) | 33 | await setAccessTokensToServers([ server ]) |
37 | 34 | ||
@@ -129,11 +126,6 @@ describe('Test videos history API validator', function () { | |||
129 | }) | 126 | }) |
130 | 127 | ||
131 | after(async function () { | 128 | after(async function () { |
132 | killallServers([ server ]) | 129 | await cleanupTests([ server ]) |
133 | |||
134 | // Keep the logs if the test failed | ||
135 | if (this['ok']) { | ||
136 | await flushTests() | ||
137 | } | ||
138 | }) | 130 | }) |
139 | }) | 131 | }) |
diff --git a/server/tests/api/check-params/videos.ts b/server/tests/api/check-params/videos.ts index f26b91435..51e592a15 100644 --- a/server/tests/api/check-params/videos.ts +++ b/server/tests/api/check-params/videos.ts | |||
@@ -6,15 +6,28 @@ import 'mocha' | |||
6 | import { join } from 'path' | 6 | import { join } from 'path' |
7 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' | 7 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' |
8 | import { | 8 | import { |
9 | createUser, flushTests, getMyUserInformation, getVideo, getVideosList, immutableAssign, killallServers, makeDeleteRequest, | 9 | cleanupTests, |
10 | makeGetRequest, makeUploadRequest, makePutBodyRequest, removeVideo, runServer, ServerInfo, setAccessTokensToServers, userLogin | 10 | createUser, |
11 | } from '../../../../shared/utils' | 11 | flushAndRunServer, |
12 | getMyUserInformation, | ||
13 | getVideo, | ||
14 | getVideosList, | ||
15 | immutableAssign, | ||
16 | makeDeleteRequest, | ||
17 | makeGetRequest, | ||
18 | makePutBodyRequest, | ||
19 | makeUploadRequest, | ||
20 | removeVideo, | ||
21 | ServerInfo, | ||
22 | setAccessTokensToServers, | ||
23 | userLogin, | ||
24 | root | ||
25 | } from '../../../../shared/extra-utils' | ||
12 | import { | 26 | import { |
13 | checkBadCountPagination, | 27 | checkBadCountPagination, |
14 | checkBadSortPagination, | 28 | checkBadSortPagination, |
15 | checkBadStartPagination | 29 | checkBadStartPagination |
16 | } from '../../../../shared/utils/requests/check-api-params' | 30 | } from '../../../../shared/extra-utils/requests/check-api-params' |
17 | import { getAccountsList } from '../../../../shared/utils/users/accounts' | ||
18 | 31 | ||
19 | const expect = chai.expect | 32 | const expect = chai.expect |
20 | 33 | ||
@@ -32,15 +45,13 @@ describe('Test videos API validator', function () { | |||
32 | before(async function () { | 45 | before(async function () { |
33 | this.timeout(30000) | 46 | this.timeout(30000) |
34 | 47 | ||
35 | await flushTests() | 48 | server = await flushAndRunServer(1) |
36 | |||
37 | server = await runServer(1) | ||
38 | 49 | ||
39 | await setAccessTokensToServers([ server ]) | 50 | await setAccessTokensToServers([ server ]) |
40 | 51 | ||
41 | const username = 'user1' | 52 | const username = 'user1' |
42 | const password = 'my super password' | 53 | const password = 'my super password' |
43 | await createUser(server.url, server.accessToken, username, password) | 54 | await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) |
44 | userAccessToken = await userLogin(server, { username, password }) | 55 | userAccessToken = await userLogin(server, { username, password }) |
45 | 56 | ||
46 | { | 57 | { |
@@ -167,7 +178,7 @@ describe('Test videos API validator', function () { | |||
167 | describe('When adding a video', function () { | 178 | describe('When adding a video', function () { |
168 | let baseCorrectParams | 179 | let baseCorrectParams |
169 | const baseCorrectAttaches = { | 180 | const baseCorrectAttaches = { |
170 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.webm') | 181 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.webm') |
171 | } | 182 | } |
172 | 183 | ||
173 | before(function () { | 184 | before(function () { |
@@ -179,12 +190,14 @@ describe('Test videos API validator', function () { | |||
179 | language: 'pt', | 190 | language: 'pt', |
180 | nsfw: false, | 191 | nsfw: false, |
181 | commentsEnabled: true, | 192 | commentsEnabled: true, |
193 | downloadEnabled: true, | ||
182 | waitTranscoding: true, | 194 | waitTranscoding: true, |
183 | description: 'my super description', | 195 | description: 'my super description', |
184 | support: 'my super support text', | 196 | support: 'my super support text', |
185 | tags: [ 'tag1', 'tag2' ], | 197 | tags: [ 'tag1', 'tag2' ], |
186 | privacy: VideoPrivacy.PUBLIC, | 198 | privacy: VideoPrivacy.PUBLIC, |
187 | channelId: channelId | 199 | channelId: channelId, |
200 | originallyPublishedAt: new Date().toISOString() | ||
188 | } | 201 | } |
189 | }) | 202 | }) |
190 | 203 | ||
@@ -262,7 +275,7 @@ describe('Test videos API validator', function () { | |||
262 | username: 'fake', | 275 | username: 'fake', |
263 | password: 'fake_password' | 276 | password: 'fake_password' |
264 | } | 277 | } |
265 | await createUser(server.url, server.accessToken, user.username, user.password) | 278 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
266 | 279 | ||
267 | const accessTokenUser = await userLogin(server, user) | 280 | const accessTokenUser = await userLogin(server, user) |
268 | const res = await getMyUserInformation(server.url, accessTokenUser) | 281 | const res = await getMyUserInformation(server.url, accessTokenUser) |
@@ -312,21 +325,28 @@ describe('Test videos API validator', function () { | |||
312 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 325 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
313 | }) | 326 | }) |
314 | 327 | ||
328 | it('Should fail with a bad originally published at attribute', async function () { | ||
329 | const fields = immutableAssign(baseCorrectParams, { 'originallyPublishedAt': 'toto' }) | ||
330 | const attaches = baseCorrectAttaches | ||
331 | |||
332 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | ||
333 | }) | ||
334 | |||
315 | it('Should fail without an input file', async function () { | 335 | it('Should fail without an input file', async function () { |
316 | const fields = baseCorrectParams | 336 | const fields = baseCorrectParams |
317 | const attaches = {} | 337 | const attaches = {} |
318 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 338 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
319 | }) | 339 | }) |
320 | 340 | ||
321 | it('Should fail without an incorrect input file', async function () { | 341 | it('Should fail with an incorrect input file', async function () { |
322 | const fields = baseCorrectParams | 342 | const fields = baseCorrectParams |
323 | let attaches = { | 343 | let attaches = { |
324 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short_fake.webm') | 344 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short_fake.webm') |
325 | } | 345 | } |
326 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 346 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
327 | 347 | ||
328 | attaches = { | 348 | attaches = { |
329 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.mkv') | 349 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mkv') |
330 | } | 350 | } |
331 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 351 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
332 | }) | 352 | }) |
@@ -334,8 +354,8 @@ describe('Test videos API validator', function () { | |||
334 | it('Should fail with an incorrect thumbnail file', async function () { | 354 | it('Should fail with an incorrect thumbnail file', async function () { |
335 | const fields = baseCorrectParams | 355 | const fields = baseCorrectParams |
336 | const attaches = { | 356 | const attaches = { |
337 | 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png'), | 357 | 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), |
338 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') | 358 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
339 | } | 359 | } |
340 | 360 | ||
341 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 361 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -344,8 +364,8 @@ describe('Test videos API validator', function () { | |||
344 | it('Should fail with a big thumbnail file', async function () { | 364 | it('Should fail with a big thumbnail file', async function () { |
345 | const fields = baseCorrectParams | 365 | const fields = baseCorrectParams |
346 | const attaches = { | 366 | const attaches = { |
347 | 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png'), | 367 | 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), |
348 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') | 368 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
349 | } | 369 | } |
350 | 370 | ||
351 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 371 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -354,8 +374,8 @@ describe('Test videos API validator', function () { | |||
354 | it('Should fail with an incorrect preview file', async function () { | 374 | it('Should fail with an incorrect preview file', async function () { |
355 | const fields = baseCorrectParams | 375 | const fields = baseCorrectParams |
356 | const attaches = { | 376 | const attaches = { |
357 | 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png'), | 377 | 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png'), |
358 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') | 378 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
359 | } | 379 | } |
360 | 380 | ||
361 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 381 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -364,8 +384,8 @@ describe('Test videos API validator', function () { | |||
364 | it('Should fail with a big preview file', async function () { | 384 | it('Should fail with a big preview file', async function () { |
365 | const fields = baseCorrectParams | 385 | const fields = baseCorrectParams |
366 | const attaches = { | 386 | const attaches = { |
367 | 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png'), | 387 | 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png'), |
368 | 'videofile': join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') | 388 | 'videofile': join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
369 | } | 389 | } |
370 | 390 | ||
371 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) | 391 | await makeUploadRequest({ url: server.url, path: path + '/upload', token: server.accessToken, fields, attaches }) |
@@ -390,7 +410,7 @@ describe('Test videos API validator', function () { | |||
390 | 410 | ||
391 | { | 411 | { |
392 | const attaches = immutableAssign(baseCorrectAttaches, { | 412 | const attaches = immutableAssign(baseCorrectAttaches, { |
393 | videofile: join(__dirname, '..', '..', 'fixtures', 'video_short.mp4') | 413 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.mp4') |
394 | }) | 414 | }) |
395 | 415 | ||
396 | await makeUploadRequest({ | 416 | await makeUploadRequest({ |
@@ -405,7 +425,7 @@ describe('Test videos API validator', function () { | |||
405 | 425 | ||
406 | { | 426 | { |
407 | const attaches = immutableAssign(baseCorrectAttaches, { | 427 | const attaches = immutableAssign(baseCorrectAttaches, { |
408 | videofile: join(__dirname, '..', '..', 'fixtures', 'video_short.ogv') | 428 | videofile: join(root(), 'server', 'tests', 'fixtures', 'video_short.ogv') |
409 | }) | 429 | }) |
410 | 430 | ||
411 | await makeUploadRequest({ | 431 | await makeUploadRequest({ |
@@ -428,6 +448,7 @@ describe('Test videos API validator', function () { | |||
428 | language: 'pt', | 448 | language: 'pt', |
429 | nsfw: false, | 449 | nsfw: false, |
430 | commentsEnabled: false, | 450 | commentsEnabled: false, |
451 | downloadEnabled: false, | ||
431 | description: 'my super description', | 452 | description: 'my super description', |
432 | privacy: VideoPrivacy.PUBLIC, | 453 | privacy: VideoPrivacy.PUBLIC, |
433 | tags: [ 'tag1', 'tag2' ] | 454 | tags: [ 'tag1', 'tag2' ] |
@@ -532,10 +553,16 @@ describe('Test videos API validator', function () { | |||
532 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) | 553 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) |
533 | }) | 554 | }) |
534 | 555 | ||
556 | it('Should fail with a bad originally published at param', async function () { | ||
557 | const fields = immutableAssign(baseCorrectParams, { originallyPublishedAt: 'toto' }) | ||
558 | |||
559 | await makePutBodyRequest({ url: server.url, path: path + videoId, token: server.accessToken, fields }) | ||
560 | }) | ||
561 | |||
535 | it('Should fail with an incorrect thumbnail file', async function () { | 562 | it('Should fail with an incorrect thumbnail file', async function () { |
536 | const fields = baseCorrectParams | 563 | const fields = baseCorrectParams |
537 | const attaches = { | 564 | const attaches = { |
538 | 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') | 565 | 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png') |
539 | } | 566 | } |
540 | 567 | ||
541 | await makeUploadRequest({ | 568 | await makeUploadRequest({ |
@@ -551,7 +578,7 @@ describe('Test videos API validator', function () { | |||
551 | it('Should fail with a big thumbnail file', async function () { | 578 | it('Should fail with a big thumbnail file', async function () { |
552 | const fields = baseCorrectParams | 579 | const fields = baseCorrectParams |
553 | const attaches = { | 580 | const attaches = { |
554 | 'thumbnailfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') | 581 | 'thumbnailfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') |
555 | } | 582 | } |
556 | 583 | ||
557 | await makeUploadRequest({ | 584 | await makeUploadRequest({ |
@@ -567,7 +594,7 @@ describe('Test videos API validator', function () { | |||
567 | it('Should fail with an incorrect preview file', async function () { | 594 | it('Should fail with an incorrect preview file', async function () { |
568 | const fields = baseCorrectParams | 595 | const fields = baseCorrectParams |
569 | const attaches = { | 596 | const attaches = { |
570 | 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar.png') | 597 | 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar.png') |
571 | } | 598 | } |
572 | 599 | ||
573 | await makeUploadRequest({ | 600 | await makeUploadRequest({ |
@@ -583,7 +610,7 @@ describe('Test videos API validator', function () { | |||
583 | it('Should fail with a big preview file', async function () { | 610 | it('Should fail with a big preview file', async function () { |
584 | const fields = baseCorrectParams | 611 | const fields = baseCorrectParams |
585 | const attaches = { | 612 | const attaches = { |
586 | 'previewfile': join(__dirname, '..', '..', 'fixtures', 'avatar-big.png') | 613 | 'previewfile': join(root(), 'server', 'tests', 'fixtures', 'avatar-big.png') |
587 | } | 614 | } |
588 | 615 | ||
589 | await makeUploadRequest({ | 616 | await makeUploadRequest({ |
@@ -714,11 +741,6 @@ describe('Test videos API validator', function () { | |||
714 | }) | 741 | }) |
715 | 742 | ||
716 | after(async function () { | 743 | after(async function () { |
717 | killallServers([ server ]) | 744 | await cleanupTests([ server ]) |
718 | |||
719 | // Keep the logs if the test failed | ||
720 | if (this['ok']) { | ||
721 | await flushTests() | ||
722 | } | ||
723 | }) | 745 | }) |
724 | }) | 746 | }) |
diff --git a/server/tests/api/index-1.ts b/server/tests/api/index-1.ts index 80d752f42..75cdd9025 100644 --- a/server/tests/api/index-1.ts +++ b/server/tests/api/index-1.ts | |||
@@ -1,2 +1,3 @@ | |||
1 | import './check-params' | 1 | import './check-params' |
2 | import './notifications' | ||
2 | import './search' | 3 | import './search' |
diff --git a/server/tests/api/notifications/index.ts b/server/tests/api/notifications/index.ts new file mode 100644 index 000000000..95ac8fc51 --- /dev/null +++ b/server/tests/api/notifications/index.ts | |||
@@ -0,0 +1 @@ | |||
export * from './user-notifications' | |||
diff --git a/server/tests/api/users/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts index 72b6a0aa2..f479e1785 100644 --- a/server/tests/api/users/user-notifications.ts +++ b/server/tests/api/notifications/user-notifications.ts | |||
@@ -4,25 +4,30 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | addVideoToBlacklist, | 6 | addVideoToBlacklist, |
7 | cleanupTests, | ||
7 | createUser, | 8 | createUser, |
8 | doubleFollow, | 9 | doubleFollow, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
10 | flushTests, | 11 | follow, |
12 | getCustomConfig, | ||
11 | getMyUserInformation, | 13 | getMyUserInformation, |
14 | getVideoCommentThreads, | ||
15 | getVideoThreadComments, | ||
12 | immutableAssign, | 16 | immutableAssign, |
13 | registerUser, | 17 | registerUser, |
14 | removeVideoFromBlacklist, | 18 | removeVideoFromBlacklist, |
15 | reportVideoAbuse, | 19 | reportVideoAbuse, |
20 | updateCustomConfig, | ||
16 | updateMyUser, | 21 | updateMyUser, |
17 | updateVideo, | 22 | updateVideo, |
18 | updateVideoChannel, | 23 | updateVideoChannel, |
19 | userLogin, | 24 | userLogin, |
20 | wait | 25 | wait |
21 | } from '../../../../shared/utils' | 26 | } from '../../../../shared/extra-utils' |
22 | import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/utils/index' | 27 | import { ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index' |
23 | import { setAccessTokensToServers } from '../../../../shared/utils/users/login' | 28 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
24 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 29 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
25 | import { getUserNotificationSocket } from '../../../../shared/utils/socket/socket-io' | 30 | import { getUserNotificationSocket } from '../../../../shared/extra-utils/socket/socket-io' |
26 | import { | 31 | import { |
27 | checkCommentMention, | 32 | checkCommentMention, |
28 | CheckerBaseParams, | 33 | CheckerBaseParams, |
@@ -30,16 +35,18 @@ import { | |||
30 | checkNewActorFollow, | 35 | checkNewActorFollow, |
31 | checkNewBlacklistOnMyVideo, | 36 | checkNewBlacklistOnMyVideo, |
32 | checkNewCommentOnMyVideo, | 37 | checkNewCommentOnMyVideo, |
38 | checkNewInstanceFollower, | ||
33 | checkNewVideoAbuseForModerators, | 39 | checkNewVideoAbuseForModerators, |
34 | checkNewVideoFromSubscription, | 40 | checkNewVideoFromSubscription, |
35 | checkUserRegistered, | 41 | checkUserRegistered, |
42 | checkVideoAutoBlacklistForModerators, | ||
36 | checkVideoIsPublished, | 43 | checkVideoIsPublished, |
37 | getLastNotification, | 44 | getLastNotification, |
38 | getUserNotifications, | 45 | getUserNotifications, |
46 | markAsReadAllNotifications, | ||
39 | markAsReadNotifications, | 47 | markAsReadNotifications, |
40 | updateMyNotificationSettings, | 48 | updateMyNotificationSettings |
41 | markAsReadAllNotifications | 49 | } from '../../../../shared/extra-utils/users/user-notifications' |
42 | } from '../../../../shared/utils/users/user-notifications' | ||
43 | import { | 50 | import { |
44 | User, | 51 | User, |
45 | UserNotification, | 52 | UserNotification, |
@@ -47,13 +54,15 @@ import { | |||
47 | UserNotificationSettingValue, | 54 | UserNotificationSettingValue, |
48 | UserNotificationType | 55 | UserNotificationType |
49 | } from '../../../../shared/models/users' | 56 | } from '../../../../shared/models/users' |
50 | import { MockSmtpServer } from '../../../../shared/utils/miscs/email' | 57 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' |
51 | import { addUserSubscription, removeUserSubscription } from '../../../../shared/utils/users/user-subscriptions' | 58 | import { addUserSubscription, removeUserSubscription } from '../../../../shared/extra-utils/users/user-subscriptions' |
52 | import { VideoPrivacy } from '../../../../shared/models/videos' | 59 | import { VideoPrivacy } from '../../../../shared/models/videos' |
53 | import { getBadVideoUrl, getYoutubeVideoUrl, importVideo } from '../../../../shared/utils/videos/video-imports' | 60 | import { getBadVideoUrl, getYoutubeVideoUrl, importVideo } from '../../../../shared/extra-utils/videos/video-imports' |
54 | import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/utils/videos/video-comments' | 61 | import { addVideoCommentReply, addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' |
55 | import * as uuidv4 from 'uuid/v4' | 62 | import * as uuidv4 from 'uuid/v4' |
56 | import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/utils/users/blocklist' | 63 | import { addAccountToAccountBlocklist, removeAccountFromAccountBlocklist } from '../../../../shared/extra-utils/users/blocklist' |
64 | import { CustomConfig } from '../../../../shared/models/server' | ||
65 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | ||
57 | 66 | ||
58 | const expect = chai.expect | 67 | const expect = chai.expect |
59 | 68 | ||
@@ -92,12 +101,14 @@ describe('Test users notifications', function () { | |||
92 | newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 101 | newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
93 | newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 102 | newCommentOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
94 | videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 103 | videoAbuseAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
104 | videoAutoBlacklistAsModerator: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | ||
95 | blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 105 | blacklistOnMyVideo: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
96 | myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 106 | myVideoImportFinished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
97 | myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 107 | myVideoPublished: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
98 | commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 108 | commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
99 | newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 109 | newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
100 | newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL | 110 | newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
111 | newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL | ||
101 | } | 112 | } |
102 | 113 | ||
103 | before(async function () { | 114 | before(async function () { |
@@ -105,14 +116,12 @@ describe('Test users notifications', function () { | |||
105 | 116 | ||
106 | await MockSmtpServer.Instance.collectEmails(emails) | 117 | await MockSmtpServer.Instance.collectEmails(emails) |
107 | 118 | ||
108 | await flushTests() | ||
109 | |||
110 | const overrideConfig = { | 119 | const overrideConfig = { |
111 | smtp: { | 120 | smtp: { |
112 | hostname: 'localhost' | 121 | hostname: 'localhost' |
113 | } | 122 | } |
114 | } | 123 | } |
115 | servers = await flushAndRunMultipleServers(2, overrideConfig) | 124 | servers = await flushAndRunMultipleServers(3, overrideConfig) |
116 | 125 | ||
117 | // Get the access tokens | 126 | // Get the access tokens |
118 | await setAccessTokensToServers(servers) | 127 | await setAccessTokensToServers(servers) |
@@ -126,7 +135,13 @@ describe('Test users notifications', function () { | |||
126 | username: 'user_1', | 135 | username: 'user_1', |
127 | password: 'super password' | 136 | password: 'super password' |
128 | } | 137 | } |
129 | await createUser(servers[0].url, servers[0].accessToken, user.username, user.password, 10 * 1000 * 1000) | 138 | await createUser({ |
139 | url: servers[ 0 ].url, | ||
140 | accessToken: servers[ 0 ].accessToken, | ||
141 | username: user.username, | ||
142 | password: user.password, | ||
143 | videoQuota: 10 * 1000 * 1000 | ||
144 | }) | ||
130 | userAccessToken = await userLogin(servers[0], user) | 145 | userAccessToken = await userLogin(servers[0], user) |
131 | 146 | ||
132 | await updateMyNotificationSettings(servers[0].url, userAccessToken, allNotificationSettings) | 147 | await updateMyNotificationSettings(servers[0].url, userAccessToken, allNotificationSettings) |
@@ -165,6 +180,8 @@ describe('Test users notifications', function () { | |||
165 | }) | 180 | }) |
166 | 181 | ||
167 | it('Should not send notifications if the user does not follow the video publisher', async function () { | 182 | it('Should not send notifications if the user does not follow the video publisher', async function () { |
183 | this.timeout(10000) | ||
184 | |||
168 | await uploadVideoByLocalAccount(servers) | 185 | await uploadVideoByLocalAccount(servers) |
169 | 186 | ||
170 | const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) | 187 | const notification = await getLastNotification(servers[ 0 ].url, userAccessToken) |
@@ -214,7 +231,7 @@ describe('Test users notifications', function () { | |||
214 | }) | 231 | }) |
215 | 232 | ||
216 | it('Should send a new video notification on a remote scheduled publication', async function () { | 233 | it('Should send a new video notification on a remote scheduled publication', async function () { |
217 | this.timeout(20000) | 234 | this.timeout(50000) |
218 | 235 | ||
219 | // In 2 seconds | 236 | // In 2 seconds |
220 | let updateAt = new Date(new Date().getTime() + 2000) | 237 | let updateAt = new Date(new Date().getTime() + 2000) |
@@ -236,7 +253,7 @@ describe('Test users notifications', function () { | |||
236 | it('Should not send a notification before the video is published', async function () { | 253 | it('Should not send a notification before the video is published', async function () { |
237 | this.timeout(20000) | 254 | this.timeout(20000) |
238 | 255 | ||
239 | let updateAt = new Date(new Date().getTime() + 100000) | 256 | let updateAt = new Date(new Date().getTime() + 1000000) |
240 | 257 | ||
241 | const data = { | 258 | const data = { |
242 | privacy: VideoPrivacy.PRIVATE, | 259 | privacy: VideoPrivacy.PRIVATE, |
@@ -303,7 +320,7 @@ describe('Test users notifications', function () { | |||
303 | }) | 320 | }) |
304 | 321 | ||
305 | it('Should send a new video notification after a video import', async function () { | 322 | it('Should send a new video notification after a video import', async function () { |
306 | this.timeout(30000) | 323 | this.timeout(100000) |
307 | 324 | ||
308 | const name = 'video import ' + uuidv4() | 325 | const name = 'video import ' + uuidv4() |
309 | 326 | ||
@@ -398,10 +415,14 @@ describe('Test users notifications', function () { | |||
398 | 415 | ||
399 | await waitJobs(servers) | 416 | await waitJobs(servers) |
400 | 417 | ||
401 | const resComment = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment') | 418 | await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment') |
402 | const commentId = resComment.body.comment.id | ||
403 | 419 | ||
404 | await waitJobs(servers) | 420 | await waitJobs(servers) |
421 | |||
422 | const resComment = await getVideoCommentThreads(servers[0].url, uuid, 0, 5) | ||
423 | expect(resComment.body.data).to.have.lengthOf(1) | ||
424 | const commentId = resComment.body.data[0].id | ||
425 | |||
405 | await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'presence') | 426 | await checkNewCommentOnMyVideo(baseParams, uuid, commentId, commentId, 'presence') |
406 | }) | 427 | }) |
407 | 428 | ||
@@ -428,13 +449,24 @@ describe('Test users notifications', function () { | |||
428 | const uuid = resVideo.body.video.uuid | 449 | const uuid = resVideo.body.video.uuid |
429 | await waitJobs(servers) | 450 | await waitJobs(servers) |
430 | 451 | ||
431 | const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'comment') | 452 | { |
432 | const threadId = resThread.body.comment.id | 453 | const resThread = await addVideoCommentThread(servers[ 1 ].url, servers[ 1 ].accessToken, uuid, 'comment') |
433 | 454 | const threadId = resThread.body.comment.id | |
434 | const resComment = await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, threadId, 'reply') | 455 | await addVideoCommentReply(servers[ 1 ].url, servers[ 1 ].accessToken, uuid, threadId, 'reply') |
435 | const commentId = resComment.body.comment.id | 456 | } |
436 | 457 | ||
437 | await waitJobs(servers) | 458 | await waitJobs(servers) |
459 | |||
460 | const resThread = await getVideoCommentThreads(servers[0].url, uuid, 0, 5) | ||
461 | expect(resThread.body.data).to.have.lengthOf(1) | ||
462 | const threadId = resThread.body.data[0].id | ||
463 | |||
464 | const resComments = await getVideoThreadComments(servers[0].url, uuid, threadId) | ||
465 | const tree = resComments.body as VideoCommentThreadTree | ||
466 | |||
467 | expect(tree.children).to.have.lengthOf(1) | ||
468 | const commentId = tree.children[0].comment.id | ||
469 | |||
438 | await checkNewCommentOnMyVideo(baseParams, uuid, commentId, threadId, 'presence') | 470 | await checkNewCommentOnMyVideo(baseParams, uuid, commentId, threadId, 'presence') |
439 | }) | 471 | }) |
440 | }) | 472 | }) |
@@ -547,17 +579,27 @@ describe('Test users notifications', function () { | |||
547 | 579 | ||
548 | await waitJobs(servers) | 580 | await waitJobs(servers) |
549 | const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'hello @user_1@localhost:9001 1') | 581 | const resThread = await addVideoCommentThread(servers[1].url, servers[1].accessToken, uuid, 'hello @user_1@localhost:9001 1') |
550 | const threadId = resThread.body.comment.id | 582 | const server2ThreadId = resThread.body.comment.id |
551 | 583 | ||
552 | await waitJobs(servers) | 584 | await waitJobs(servers) |
553 | await checkCommentMention(baseParams, uuid, threadId, threadId, 'super root 2 name', 'presence') | 585 | |
586 | const resThread2 = await getVideoCommentThreads(servers[0].url, uuid, 0, 5) | ||
587 | expect(resThread2.body.data).to.have.lengthOf(1) | ||
588 | const server1ThreadId = resThread2.body.data[0].id | ||
589 | await checkCommentMention(baseParams, uuid, server1ThreadId, server1ThreadId, 'super root 2 name', 'presence') | ||
554 | 590 | ||
555 | const text = '@user_1@localhost:9001 hello 2 @root@localhost:9001' | 591 | const text = '@user_1@localhost:9001 hello 2 @root@localhost:9001' |
556 | const resComment = await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, threadId, text) | 592 | await addVideoCommentReply(servers[1].url, servers[1].accessToken, uuid, server2ThreadId, text) |
557 | const commentId = resComment.body.comment.id | ||
558 | 593 | ||
559 | await waitJobs(servers) | 594 | await waitJobs(servers) |
560 | await checkCommentMention(baseParams, uuid, commentId, threadId, 'super root 2 name', 'presence') | 595 | |
596 | const resComments = await getVideoThreadComments(servers[0].url, uuid, server1ThreadId) | ||
597 | const tree = resComments.body as VideoCommentThreadTree | ||
598 | |||
599 | expect(tree.children).to.have.lengthOf(1) | ||
600 | const commentId = tree.children[0].comment.id | ||
601 | |||
602 | await checkCommentMention(baseParams, uuid, commentId, server1ThreadId, 'super root 2 name', 'presence') | ||
561 | }) | 603 | }) |
562 | }) | 604 | }) |
563 | 605 | ||
@@ -658,6 +700,8 @@ describe('Test users notifications', function () { | |||
658 | }) | 700 | }) |
659 | 701 | ||
660 | it('Should not send a notification if transcoding is not enabled', async function () { | 702 | it('Should not send a notification if transcoding is not enabled', async function () { |
703 | this.timeout(10000) | ||
704 | |||
661 | const { name, uuid } = await uploadVideoByLocalAccount(servers) | 705 | const { name, uuid } = await uploadVideoByLocalAccount(servers) |
662 | await waitJobs(servers) | 706 | await waitJobs(servers) |
663 | 707 | ||
@@ -731,6 +775,24 @@ describe('Test users notifications', function () { | |||
731 | await wait(6000) | 775 | await wait(6000) |
732 | await checkVideoIsPublished(baseParams, name, uuid, 'presence') | 776 | await checkVideoIsPublished(baseParams, name, uuid, 'presence') |
733 | }) | 777 | }) |
778 | |||
779 | it('Should not send a notification before the video is published', async function () { | ||
780 | this.timeout(20000) | ||
781 | |||
782 | let updateAt = new Date(new Date().getTime() + 100000) | ||
783 | |||
784 | const data = { | ||
785 | privacy: VideoPrivacy.PRIVATE, | ||
786 | scheduleUpdate: { | ||
787 | updateAt: updateAt.toISOString(), | ||
788 | privacy: VideoPrivacy.PUBLIC | ||
789 | } | ||
790 | } | ||
791 | const { name, uuid } = await uploadVideoByRemoteAccount(servers, data) | ||
792 | |||
793 | await wait(6000) | ||
794 | await checkVideoIsPublished(baseParams, name, uuid, 'absence') | ||
795 | }) | ||
734 | }) | 796 | }) |
735 | 797 | ||
736 | describe('My video is imported', function () { | 798 | describe('My video is imported', function () { |
@@ -795,6 +857,8 @@ describe('Test users notifications', function () { | |||
795 | }) | 857 | }) |
796 | 858 | ||
797 | it('Should send a notification only to moderators when a user registers on the instance', async function () { | 859 | it('Should send a notification only to moderators when a user registers on the instance', async function () { |
860 | this.timeout(10000) | ||
861 | |||
798 | await registerUser(servers[0].url, 'user_45', 'password') | 862 | await registerUser(servers[0].url, 'user_45', 'password') |
799 | 863 | ||
800 | await waitJobs(servers) | 864 | await waitJobs(servers) |
@@ -806,6 +870,32 @@ describe('Test users notifications', function () { | |||
806 | }) | 870 | }) |
807 | }) | 871 | }) |
808 | 872 | ||
873 | describe('New instance follower', function () { | ||
874 | let baseParams: CheckerBaseParams | ||
875 | |||
876 | before(async () => { | ||
877 | baseParams = { | ||
878 | server: servers[0], | ||
879 | emails, | ||
880 | socketNotifications: adminNotifications, | ||
881 | token: servers[0].accessToken | ||
882 | } | ||
883 | }) | ||
884 | |||
885 | it('Should send a notification only to admin when there is a new instance follower', async function () { | ||
886 | this.timeout(20000) | ||
887 | |||
888 | await follow(servers[2].url, [ servers[0].url ], servers[2].accessToken) | ||
889 | |||
890 | await waitJobs(servers) | ||
891 | |||
892 | await checkNewInstanceFollower(baseParams, 'localhost:9003', 'presence') | ||
893 | |||
894 | const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } | ||
895 | await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:9003', 'absence') | ||
896 | }) | ||
897 | }) | ||
898 | |||
809 | describe('New actor follow', function () { | 899 | describe('New actor follow', function () { |
810 | let baseParams: CheckerBaseParams | 900 | let baseParams: CheckerBaseParams |
811 | let myChannelName = 'super channel name' | 901 | let myChannelName = 'super channel name' |
@@ -863,6 +953,8 @@ describe('Test users notifications', function () { | |||
863 | }) | 953 | }) |
864 | 954 | ||
865 | it('Should notify when a local account is following one of our channel', async function () { | 955 | it('Should notify when a local account is following one of our channel', async function () { |
956 | this.timeout(10000) | ||
957 | |||
866 | await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:9001') | 958 | await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1@localhost:9001') |
867 | 959 | ||
868 | await waitJobs(servers) | 960 | await waitJobs(servers) |
@@ -871,6 +963,8 @@ describe('Test users notifications', function () { | |||
871 | }) | 963 | }) |
872 | 964 | ||
873 | it('Should notify when a remote account is following one of our channel', async function () { | 965 | it('Should notify when a remote account is following one of our channel', async function () { |
966 | this.timeout(10000) | ||
967 | |||
874 | await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:9001') | 968 | await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1@localhost:9001') |
875 | 969 | ||
876 | await waitJobs(servers) | 970 | await waitJobs(servers) |
@@ -879,6 +973,180 @@ describe('Test users notifications', function () { | |||
879 | }) | 973 | }) |
880 | }) | 974 | }) |
881 | 975 | ||
976 | describe('Video-related notifications when video auto-blacklist is enabled', function () { | ||
977 | let userBaseParams: CheckerBaseParams | ||
978 | let adminBaseParamsServer1: CheckerBaseParams | ||
979 | let adminBaseParamsServer2: CheckerBaseParams | ||
980 | let videoUUID: string | ||
981 | let videoName: string | ||
982 | let currentCustomConfig: CustomConfig | ||
983 | |||
984 | before(async () => { | ||
985 | |||
986 | adminBaseParamsServer1 = { | ||
987 | server: servers[0], | ||
988 | emails, | ||
989 | socketNotifications: adminNotifications, | ||
990 | token: servers[0].accessToken | ||
991 | } | ||
992 | |||
993 | adminBaseParamsServer2 = { | ||
994 | server: servers[1], | ||
995 | emails, | ||
996 | socketNotifications: adminNotificationsServer2, | ||
997 | token: servers[1].accessToken | ||
998 | } | ||
999 | |||
1000 | userBaseParams = { | ||
1001 | server: servers[0], | ||
1002 | emails, | ||
1003 | socketNotifications: userNotifications, | ||
1004 | token: userAccessToken | ||
1005 | } | ||
1006 | |||
1007 | const resCustomConfig = await getCustomConfig(servers[0].url, servers[0].accessToken) | ||
1008 | currentCustomConfig = resCustomConfig.body | ||
1009 | const autoBlacklistTestsCustomConfig = immutableAssign(currentCustomConfig, { | ||
1010 | autoBlacklist: { | ||
1011 | videos: { | ||
1012 | ofUsers: { | ||
1013 | enabled: true | ||
1014 | } | ||
1015 | } | ||
1016 | } | ||
1017 | }) | ||
1018 | // enable transcoding otherwise own publish notification after transcoding not expected | ||
1019 | autoBlacklistTestsCustomConfig.transcoding.enabled = true | ||
1020 | await updateCustomConfig(servers[0].url, servers[0].accessToken, autoBlacklistTestsCustomConfig) | ||
1021 | |||
1022 | await addUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001') | ||
1023 | await addUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001') | ||
1024 | |||
1025 | }) | ||
1026 | |||
1027 | it('Should send notification to moderators on new video with auto-blacklist', async function () { | ||
1028 | this.timeout(20000) | ||
1029 | |||
1030 | videoName = 'video with auto-blacklist ' + uuidv4() | ||
1031 | const resVideo = await uploadVideo(servers[0].url, userAccessToken, { name: videoName }) | ||
1032 | videoUUID = resVideo.body.video.uuid | ||
1033 | |||
1034 | await waitJobs(servers) | ||
1035 | await checkVideoAutoBlacklistForModerators(adminBaseParamsServer1, videoUUID, videoName, 'presence') | ||
1036 | }) | ||
1037 | |||
1038 | it('Should not send video publish notification if auto-blacklisted', async function () { | ||
1039 | await checkVideoIsPublished(userBaseParams, videoName, videoUUID, 'absence') | ||
1040 | }) | ||
1041 | |||
1042 | it('Should not send a local user subscription notification if auto-blacklisted', async function () { | ||
1043 | await checkNewVideoFromSubscription(adminBaseParamsServer1, videoName, videoUUID, 'absence') | ||
1044 | }) | ||
1045 | |||
1046 | it('Should not send a remote user subscription notification if auto-blacklisted', async function () { | ||
1047 | await checkNewVideoFromSubscription(adminBaseParamsServer2, videoName, videoUUID, 'absence') | ||
1048 | }) | ||
1049 | |||
1050 | it('Should send video published and unblacklist after video unblacklisted', async function () { | ||
1051 | this.timeout(20000) | ||
1052 | |||
1053 | await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, videoUUID) | ||
1054 | |||
1055 | await waitJobs(servers) | ||
1056 | |||
1057 | // FIXME: Can't test as two notifications sent to same user and util only checks last one | ||
1058 | // One notification might be better anyways | ||
1059 | // await checkNewBlacklistOnMyVideo(userBaseParams, videoUUID, videoName, 'unblacklist') | ||
1060 | // await checkVideoIsPublished(userBaseParams, videoName, videoUUID, 'presence') | ||
1061 | }) | ||
1062 | |||
1063 | it('Should send a local user subscription notification after removed from blacklist', async function () { | ||
1064 | await checkNewVideoFromSubscription(adminBaseParamsServer1, videoName, videoUUID, 'presence') | ||
1065 | }) | ||
1066 | |||
1067 | it('Should send a remote user subscription notification after removed from blacklist', async function () { | ||
1068 | await checkNewVideoFromSubscription(adminBaseParamsServer2, videoName, videoUUID, 'presence') | ||
1069 | }) | ||
1070 | |||
1071 | it('Should send unblacklist but not published/subscription notes after unblacklisted if scheduled update pending', async function () { | ||
1072 | this.timeout(20000) | ||
1073 | |||
1074 | let updateAt = new Date(new Date().getTime() + 100000) | ||
1075 | |||
1076 | const name = 'video with auto-blacklist and future schedule ' + uuidv4() | ||
1077 | |||
1078 | const data = { | ||
1079 | name, | ||
1080 | privacy: VideoPrivacy.PRIVATE, | ||
1081 | scheduleUpdate: { | ||
1082 | updateAt: updateAt.toISOString(), | ||
1083 | privacy: VideoPrivacy.PUBLIC | ||
1084 | } | ||
1085 | } | ||
1086 | |||
1087 | const resVideo = await uploadVideo(servers[0].url, userAccessToken, data) | ||
1088 | const uuid = resVideo.body.video.uuid | ||
1089 | |||
1090 | await removeVideoFromBlacklist(servers[0].url, servers[0].accessToken, uuid) | ||
1091 | |||
1092 | await waitJobs(servers) | ||
1093 | await checkNewBlacklistOnMyVideo(userBaseParams, uuid, name, 'unblacklist') | ||
1094 | |||
1095 | // FIXME: Can't test absence as two notifications sent to same user and util only checks last one | ||
1096 | // One notification might be better anyways | ||
1097 | // await checkVideoIsPublished(userBaseParams, name, uuid, 'absence') | ||
1098 | |||
1099 | await checkNewVideoFromSubscription(adminBaseParamsServer1, name, uuid, 'absence') | ||
1100 | await checkNewVideoFromSubscription(adminBaseParamsServer2, name, uuid, 'absence') | ||
1101 | }) | ||
1102 | |||
1103 | it('Should not send publish/subscription notifications after scheduled update if video still auto-blacklisted', async function () { | ||
1104 | this.timeout(20000) | ||
1105 | |||
1106 | // In 2 seconds | ||
1107 | let updateAt = new Date(new Date().getTime() + 2000) | ||
1108 | |||
1109 | const name = 'video with schedule done and still auto-blacklisted ' + uuidv4() | ||
1110 | |||
1111 | const data = { | ||
1112 | name, | ||
1113 | privacy: VideoPrivacy.PRIVATE, | ||
1114 | scheduleUpdate: { | ||
1115 | updateAt: updateAt.toISOString(), | ||
1116 | privacy: VideoPrivacy.PUBLIC | ||
1117 | } | ||
1118 | } | ||
1119 | |||
1120 | const resVideo = await uploadVideo(servers[0].url, userAccessToken, data) | ||
1121 | const uuid = resVideo.body.video.uuid | ||
1122 | |||
1123 | await wait(6000) | ||
1124 | await checkVideoIsPublished(userBaseParams, name, uuid, 'absence') | ||
1125 | await checkNewVideoFromSubscription(adminBaseParamsServer1, name, uuid, 'absence') | ||
1126 | await checkNewVideoFromSubscription(adminBaseParamsServer2, name, uuid, 'absence') | ||
1127 | }) | ||
1128 | |||
1129 | it('Should not send a notification to moderators on new video without auto-blacklist', async function () { | ||
1130 | this.timeout(20000) | ||
1131 | |||
1132 | const name = 'video without auto-blacklist ' + uuidv4() | ||
1133 | |||
1134 | // admin with blacklist right will not be auto-blacklisted | ||
1135 | const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { name }) | ||
1136 | const uuid = resVideo.body.video.uuid | ||
1137 | |||
1138 | await waitJobs(servers) | ||
1139 | await checkVideoAutoBlacklistForModerators(adminBaseParamsServer1, uuid, name, 'absence') | ||
1140 | }) | ||
1141 | |||
1142 | after(async () => { | ||
1143 | await updateCustomConfig(servers[0].url, servers[0].accessToken, currentCustomConfig) | ||
1144 | |||
1145 | await removeUserSubscription(servers[0].url, servers[0].accessToken, 'user_1_channel@localhost:9001') | ||
1146 | await removeUserSubscription(servers[1].url, servers[1].accessToken, 'user_1_channel@localhost:9001') | ||
1147 | }) | ||
1148 | }) | ||
1149 | |||
882 | describe('Mark as read', function () { | 1150 | describe('Mark as read', function () { |
883 | it('Should mark as read some notifications', async function () { | 1151 | it('Should mark as read some notifications', async function () { |
884 | const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3) | 1152 | const res = await getUserNotifications(servers[ 0 ].url, userAccessToken, 2, 3) |
@@ -940,6 +1208,8 @@ describe('Test users notifications', function () { | |||
940 | }) | 1208 | }) |
941 | 1209 | ||
942 | it('Should not have notifications', async function () { | 1210 | it('Should not have notifications', async function () { |
1211 | this.timeout(20000) | ||
1212 | |||
943 | await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, { | 1213 | await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, { |
944 | newVideoFromSubscription: UserNotificationSettingValue.NONE | 1214 | newVideoFromSubscription: UserNotificationSettingValue.NONE |
945 | })) | 1215 | })) |
@@ -957,6 +1227,8 @@ describe('Test users notifications', function () { | |||
957 | }) | 1227 | }) |
958 | 1228 | ||
959 | it('Should only have web notifications', async function () { | 1229 | it('Should only have web notifications', async function () { |
1230 | this.timeout(20000) | ||
1231 | |||
960 | await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, { | 1232 | await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, { |
961 | newVideoFromSubscription: UserNotificationSettingValue.WEB | 1233 | newVideoFromSubscription: UserNotificationSettingValue.WEB |
962 | })) | 1234 | })) |
@@ -981,6 +1253,8 @@ describe('Test users notifications', function () { | |||
981 | }) | 1253 | }) |
982 | 1254 | ||
983 | it('Should only have mail notifications', async function () { | 1255 | it('Should only have mail notifications', async function () { |
1256 | this.timeout(20000) | ||
1257 | |||
984 | await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, { | 1258 | await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, { |
985 | newVideoFromSubscription: UserNotificationSettingValue.EMAIL | 1259 | newVideoFromSubscription: UserNotificationSettingValue.EMAIL |
986 | })) | 1260 | })) |
@@ -1005,6 +1279,8 @@ describe('Test users notifications', function () { | |||
1005 | }) | 1279 | }) |
1006 | 1280 | ||
1007 | it('Should have email and web notifications', async function () { | 1281 | it('Should have email and web notifications', async function () { |
1282 | this.timeout(20000) | ||
1283 | |||
1008 | await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, { | 1284 | await updateMyNotificationSettings(servers[0].url, userAccessToken, immutableAssign(allNotificationSettings, { |
1009 | newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL | 1285 | newVideoFromSubscription: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL |
1010 | })) | 1286 | })) |
@@ -1026,6 +1302,6 @@ describe('Test users notifications', function () { | |||
1026 | after(async function () { | 1302 | after(async function () { |
1027 | MockSmtpServer.Instance.kill() | 1303 | MockSmtpServer.Instance.kill() |
1028 | 1304 | ||
1029 | killallServers(servers) | 1305 | await cleanupTests(servers) |
1030 | }) | 1306 | }) |
1031 | }) | 1307 | }) |
diff --git a/server/tests/api/redundancy/redundancy.ts b/server/tests/api/redundancy/redundancy.ts index 9d3ce8153..e31329c25 100644 --- a/server/tests/api/redundancy/redundancy.ts +++ b/server/tests/api/redundancy/redundancy.ts | |||
@@ -4,30 +4,36 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoDetails } from '../../../../shared/models/videos' | 5 | import { VideoDetails } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | checkSegmentHash, | ||
8 | checkVideoFilesWereRemoved, cleanupTests, | ||
7 | doubleFollow, | 9 | doubleFollow, |
8 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
9 | getFollowingListPaginationAndSort, | 11 | getFollowingListPaginationAndSort, |
10 | getVideo, | 12 | getVideo, |
13 | getVideoWithToken, | ||
11 | immutableAssign, | 14 | immutableAssign, |
12 | killallServers, makeGetRequest, | 15 | killallServers, |
16 | makeGetRequest, | ||
17 | removeVideo, | ||
18 | reRunServer, | ||
13 | root, | 19 | root, |
14 | ServerInfo, | 20 | ServerInfo, |
15 | setAccessTokensToServers, unfollow, | 21 | setAccessTokensToServers, |
22 | unfollow, | ||
16 | uploadVideo, | 23 | uploadVideo, |
17 | viewVideo, | 24 | viewVideo, |
18 | wait, | 25 | wait, |
19 | waitUntilLog, | 26 | waitUntilLog |
20 | checkVideoFilesWereRemoved, removeVideo, getVideoWithToken | 27 | } from '../../../../shared/extra-utils' |
21 | } from '../../../../shared/utils' | 28 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
22 | import { waitJobs } from '../../../../shared/utils/server/jobs' | ||
23 | 29 | ||
24 | import * as magnetUtil from 'magnet-uri' | 30 | import * as magnetUtil from 'magnet-uri' |
25 | import { updateRedundancy } from '../../../../shared/utils/server/redundancy' | 31 | import { updateRedundancy } from '../../../../shared/extra-utils/server/redundancy' |
26 | import { ActorFollow } from '../../../../shared/models/actors' | 32 | import { ActorFollow } from '../../../../shared/models/actors' |
27 | import { readdir } from 'fs-extra' | 33 | import { readdir } from 'fs-extra' |
28 | import { join } from 'path' | 34 | import { join } from 'path' |
29 | import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy' | 35 | import { VideoRedundancyStrategy } from '../../../../shared/models/redundancy' |
30 | import { getStats } from '../../../../shared/utils/server/stats' | 36 | import { getStats } from '../../../../shared/extra-utils/server/stats' |
31 | import { ServerStats } from '../../../../shared/models/server/server-stats.model' | 37 | import { ServerStats } from '../../../../shared/models/server/server-stats.model' |
32 | 38 | ||
33 | const expect = chai.expect | 39 | const expect = chai.expect |
@@ -46,8 +52,13 @@ function checkMagnetWebseeds (file: { magnetUri: string, resolution: { id: numbe | |||
46 | expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length) | 52 | expect(parsed.urlList).to.have.lengthOf(baseWebseeds.length) |
47 | } | 53 | } |
48 | 54 | ||
49 | async function runServers (strategy: VideoRedundancyStrategy, additionalParams: any = {}) { | 55 | async function flushAndRunServers (strategy: VideoRedundancyStrategy, additionalParams: any = {}) { |
50 | const config = { | 56 | const config = { |
57 | transcoding: { | ||
58 | hls: { | ||
59 | enabled: true | ||
60 | } | ||
61 | }, | ||
51 | redundancy: { | 62 | redundancy: { |
52 | videos: { | 63 | videos: { |
53 | check_interval: '5 seconds', | 64 | check_interval: '5 seconds', |
@@ -85,7 +96,7 @@ async function runServers (strategy: VideoRedundancyStrategy, additionalParams: | |||
85 | await waitJobs(servers) | 96 | await waitJobs(servers) |
86 | } | 97 | } |
87 | 98 | ||
88 | async function check1WebSeed (strategy: VideoRedundancyStrategy, videoUUID?: string) { | 99 | async function check1WebSeed (videoUUID?: string) { |
89 | if (!videoUUID) videoUUID = video1Server2UUID | 100 | if (!videoUUID) videoUUID = video1Server2UUID |
90 | 101 | ||
91 | const webseeds = [ | 102 | const webseeds = [ |
@@ -93,47 +104,17 @@ async function check1WebSeed (strategy: VideoRedundancyStrategy, videoUUID?: str | |||
93 | ] | 104 | ] |
94 | 105 | ||
95 | for (const server of servers) { | 106 | for (const server of servers) { |
96 | { | 107 | // With token to avoid issues with video follow constraints |
97 | // With token to avoid issues with video follow constraints | 108 | const res = await getVideoWithToken(server.url, server.accessToken, videoUUID) |
98 | const res = await getVideoWithToken(server.url, server.accessToken, videoUUID) | ||
99 | 109 | ||
100 | const video: VideoDetails = res.body | 110 | const video: VideoDetails = res.body |
101 | for (const f of video.files) { | 111 | for (const f of video.files) { |
102 | checkMagnetWebseeds(f, webseeds, server) | 112 | checkMagnetWebseeds(f, webseeds, server) |
103 | } | ||
104 | } | 113 | } |
105 | } | 114 | } |
106 | } | 115 | } |
107 | 116 | ||
108 | async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategy) { | 117 | async function check2Webseeds (videoUUID?: string) { |
109 | const res = await getStats(servers[0].url) | ||
110 | const data: ServerStats = res.body | ||
111 | |||
112 | expect(data.videosRedundancy).to.have.lengthOf(1) | ||
113 | const stat = data.videosRedundancy[0] | ||
114 | |||
115 | expect(stat.strategy).to.equal(strategy) | ||
116 | expect(stat.totalSize).to.equal(204800) | ||
117 | expect(stat.totalUsed).to.be.at.least(1).and.below(204801) | ||
118 | expect(stat.totalVideoFiles).to.equal(4) | ||
119 | expect(stat.totalVideos).to.equal(1) | ||
120 | } | ||
121 | |||
122 | async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategy) { | ||
123 | const res = await getStats(servers[0].url) | ||
124 | const data: ServerStats = res.body | ||
125 | |||
126 | expect(data.videosRedundancy).to.have.lengthOf(1) | ||
127 | |||
128 | const stat = data.videosRedundancy[0] | ||
129 | expect(stat.strategy).to.equal(strategy) | ||
130 | expect(stat.totalSize).to.equal(204800) | ||
131 | expect(stat.totalUsed).to.equal(0) | ||
132 | expect(stat.totalVideoFiles).to.equal(0) | ||
133 | expect(stat.totalVideos).to.equal(0) | ||
134 | } | ||
135 | |||
136 | async function check2Webseeds (strategy: VideoRedundancyStrategy, videoUUID?: string) { | ||
137 | if (!videoUUID) videoUUID = video1Server2UUID | 118 | if (!videoUUID) videoUUID = video1Server2UUID |
138 | 119 | ||
139 | const webseeds = [ | 120 | const webseeds = [ |
@@ -158,7 +139,7 @@ async function check2Webseeds (strategy: VideoRedundancyStrategy, videoUUID?: st | |||
158 | await makeGetRequest({ | 139 | await makeGetRequest({ |
159 | url: servers[1].url, | 140 | url: servers[1].url, |
160 | statusCodeExpected: 200, | 141 | statusCodeExpected: 200, |
161 | path: '/static/webseed/' + `${videoUUID}-${file.resolution.id}.mp4`, | 142 | path: `/static/webseed/${videoUUID}-${file.resolution.id}.mp4`, |
162 | contentType: null | 143 | contentType: null |
163 | }) | 144 | }) |
164 | } | 145 | } |
@@ -174,6 +155,85 @@ async function check2Webseeds (strategy: VideoRedundancyStrategy, videoUUID?: st | |||
174 | } | 155 | } |
175 | } | 156 | } |
176 | 157 | ||
158 | async function check0PlaylistRedundancies (videoUUID?: string) { | ||
159 | if (!videoUUID) videoUUID = video1Server2UUID | ||
160 | |||
161 | for (const server of servers) { | ||
162 | // With token to avoid issues with video follow constraints | ||
163 | const res = await getVideoWithToken(server.url, server.accessToken, videoUUID) | ||
164 | const video: VideoDetails = res.body | ||
165 | |||
166 | expect(video.streamingPlaylists).to.be.an('array') | ||
167 | expect(video.streamingPlaylists).to.have.lengthOf(1) | ||
168 | expect(video.streamingPlaylists[0].redundancies).to.have.lengthOf(0) | ||
169 | } | ||
170 | } | ||
171 | |||
172 | async function check1PlaylistRedundancies (videoUUID?: string) { | ||
173 | if (!videoUUID) videoUUID = video1Server2UUID | ||
174 | |||
175 | for (const server of servers) { | ||
176 | const res = await getVideo(server.url, videoUUID) | ||
177 | const video: VideoDetails = res.body | ||
178 | |||
179 | expect(video.streamingPlaylists).to.have.lengthOf(1) | ||
180 | expect(video.streamingPlaylists[0].redundancies).to.have.lengthOf(1) | ||
181 | |||
182 | const redundancy = video.streamingPlaylists[0].redundancies[0] | ||
183 | |||
184 | expect(redundancy.baseUrl).to.equal(servers[0].url + '/static/redundancy/hls/' + videoUUID) | ||
185 | } | ||
186 | |||
187 | const baseUrlPlaylist = servers[1].url + '/static/streaming-playlists/hls' | ||
188 | const baseUrlSegment = servers[0].url + '/static/redundancy/hls' | ||
189 | |||
190 | const res = await getVideo(servers[0].url, videoUUID) | ||
191 | const hlsPlaylist = (res.body as VideoDetails).streamingPlaylists[0] | ||
192 | |||
193 | for (const resolution of [ 240, 360, 480, 720 ]) { | ||
194 | await checkSegmentHash(baseUrlPlaylist, baseUrlSegment, videoUUID, resolution, hlsPlaylist) | ||
195 | } | ||
196 | |||
197 | for (const directory of [ 'test1/redundancy/hls', 'test2/streaming-playlists/hls' ]) { | ||
198 | const files = await readdir(join(root(), directory, videoUUID)) | ||
199 | expect(files).to.have.length.at.least(4) | ||
200 | |||
201 | for (const resolution of [ 240, 360, 480, 720 ]) { | ||
202 | const filename = `${videoUUID}-${resolution}-fragmented.mp4` | ||
203 | |||
204 | expect(files.find(f => f === filename)).to.not.be.undefined | ||
205 | } | ||
206 | } | ||
207 | } | ||
208 | |||
209 | async function checkStatsWith2Webseed (strategy: VideoRedundancyStrategy) { | ||
210 | const res = await getStats(servers[0].url) | ||
211 | const data: ServerStats = res.body | ||
212 | |||
213 | expect(data.videosRedundancy).to.have.lengthOf(1) | ||
214 | const stat = data.videosRedundancy[0] | ||
215 | |||
216 | expect(stat.strategy).to.equal(strategy) | ||
217 | expect(stat.totalSize).to.equal(204800) | ||
218 | expect(stat.totalUsed).to.be.at.least(1).and.below(204801) | ||
219 | expect(stat.totalVideoFiles).to.equal(4) | ||
220 | expect(stat.totalVideos).to.equal(1) | ||
221 | } | ||
222 | |||
223 | async function checkStatsWith1Webseed (strategy: VideoRedundancyStrategy) { | ||
224 | const res = await getStats(servers[0].url) | ||
225 | const data: ServerStats = res.body | ||
226 | |||
227 | expect(data.videosRedundancy).to.have.lengthOf(1) | ||
228 | |||
229 | const stat = data.videosRedundancy[0] | ||
230 | expect(stat.strategy).to.equal(strategy) | ||
231 | expect(stat.totalSize).to.equal(204800) | ||
232 | expect(stat.totalUsed).to.equal(0) | ||
233 | expect(stat.totalVideoFiles).to.equal(0) | ||
234 | expect(stat.totalVideos).to.equal(0) | ||
235 | } | ||
236 | |||
177 | async function enableRedundancyOnServer1 () { | 237 | async function enableRedundancyOnServer1 () { |
178 | await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, true) | 238 | await updateRedundancy(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ].host, true) |
179 | 239 | ||
@@ -204,10 +264,6 @@ async function disableRedundancyOnServer1 () { | |||
204 | expect(server2.following.hostRedundancyAllowed).to.be.false | 264 | expect(server2.following.hostRedundancyAllowed).to.be.false |
205 | } | 265 | } |
206 | 266 | ||
207 | async function cleanServers () { | ||
208 | killallServers(servers) | ||
209 | } | ||
210 | |||
211 | describe('Test videos redundancy', function () { | 267 | describe('Test videos redundancy', function () { |
212 | 268 | ||
213 | describe('With most-views strategy', function () { | 269 | describe('With most-views strategy', function () { |
@@ -216,11 +272,12 @@ describe('Test videos redundancy', function () { | |||
216 | before(function () { | 272 | before(function () { |
217 | this.timeout(120000) | 273 | this.timeout(120000) |
218 | 274 | ||
219 | return runServers(strategy) | 275 | return flushAndRunServers(strategy) |
220 | }) | 276 | }) |
221 | 277 | ||
222 | it('Should have 1 webseed on the first video', async function () { | 278 | it('Should have 1 webseed on the first video', async function () { |
223 | await check1WebSeed(strategy) | 279 | await check1WebSeed() |
280 | await check0PlaylistRedundancies() | ||
224 | await checkStatsWith1Webseed(strategy) | 281 | await checkStatsWith1Webseed(strategy) |
225 | }) | 282 | }) |
226 | 283 | ||
@@ -229,31 +286,33 @@ describe('Test videos redundancy', function () { | |||
229 | }) | 286 | }) |
230 | 287 | ||
231 | it('Should have 2 webseeds on the first video', async function () { | 288 | it('Should have 2 webseeds on the first video', async function () { |
232 | this.timeout(40000) | 289 | this.timeout(80000) |
233 | 290 | ||
234 | await waitJobs(servers) | 291 | await waitJobs(servers) |
235 | await waitUntilLog(servers[0], 'Duplicated ', 4) | 292 | await waitUntilLog(servers[0], 'Duplicated ', 5) |
236 | await waitJobs(servers) | 293 | await waitJobs(servers) |
237 | 294 | ||
238 | await check2Webseeds(strategy) | 295 | await check2Webseeds() |
296 | await check1PlaylistRedundancies() | ||
239 | await checkStatsWith2Webseed(strategy) | 297 | await checkStatsWith2Webseed(strategy) |
240 | }) | 298 | }) |
241 | 299 | ||
242 | it('Should undo redundancy on server 1 and remove duplicated videos', async function () { | 300 | it('Should undo redundancy on server 1 and remove duplicated videos', async function () { |
243 | this.timeout(40000) | 301 | this.timeout(80000) |
244 | 302 | ||
245 | await disableRedundancyOnServer1() | 303 | await disableRedundancyOnServer1() |
246 | 304 | ||
247 | await waitJobs(servers) | 305 | await waitJobs(servers) |
248 | await wait(5000) | 306 | await wait(5000) |
249 | 307 | ||
250 | await check1WebSeed(strategy) | 308 | await check1WebSeed() |
309 | await check0PlaylistRedundancies() | ||
251 | 310 | ||
252 | await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ]) | 311 | await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos', join('playlists', 'hls') ]) |
253 | }) | 312 | }) |
254 | 313 | ||
255 | after(function () { | 314 | after(async function () { |
256 | return cleanServers() | 315 | return cleanupTests(servers) |
257 | }) | 316 | }) |
258 | }) | 317 | }) |
259 | 318 | ||
@@ -263,11 +322,12 @@ describe('Test videos redundancy', function () { | |||
263 | before(function () { | 322 | before(function () { |
264 | this.timeout(120000) | 323 | this.timeout(120000) |
265 | 324 | ||
266 | return runServers(strategy) | 325 | return flushAndRunServers(strategy) |
267 | }) | 326 | }) |
268 | 327 | ||
269 | it('Should have 1 webseed on the first video', async function () { | 328 | it('Should have 1 webseed on the first video', async function () { |
270 | await check1WebSeed(strategy) | 329 | await check1WebSeed() |
330 | await check0PlaylistRedundancies() | ||
271 | await checkStatsWith1Webseed(strategy) | 331 | await checkStatsWith1Webseed(strategy) |
272 | }) | 332 | }) |
273 | 333 | ||
@@ -276,31 +336,33 @@ describe('Test videos redundancy', function () { | |||
276 | }) | 336 | }) |
277 | 337 | ||
278 | it('Should have 2 webseeds on the first video', async function () { | 338 | it('Should have 2 webseeds on the first video', async function () { |
279 | this.timeout(40000) | 339 | this.timeout(80000) |
280 | 340 | ||
281 | await waitJobs(servers) | 341 | await waitJobs(servers) |
282 | await waitUntilLog(servers[0], 'Duplicated ', 4) | 342 | await waitUntilLog(servers[0], 'Duplicated ', 5) |
283 | await waitJobs(servers) | 343 | await waitJobs(servers) |
284 | 344 | ||
285 | await check2Webseeds(strategy) | 345 | await check2Webseeds() |
346 | await check1PlaylistRedundancies() | ||
286 | await checkStatsWith2Webseed(strategy) | 347 | await checkStatsWith2Webseed(strategy) |
287 | }) | 348 | }) |
288 | 349 | ||
289 | it('Should unfollow on server 1 and remove duplicated videos', async function () { | 350 | it('Should unfollow on server 1 and remove duplicated videos', async function () { |
290 | this.timeout(40000) | 351 | this.timeout(80000) |
291 | 352 | ||
292 | await unfollow(servers[0].url, servers[0].accessToken, servers[1]) | 353 | await unfollow(servers[0].url, servers[0].accessToken, servers[1]) |
293 | 354 | ||
294 | await waitJobs(servers) | 355 | await waitJobs(servers) |
295 | await wait(5000) | 356 | await wait(5000) |
296 | 357 | ||
297 | await check1WebSeed(strategy) | 358 | await check1WebSeed() |
359 | await check0PlaylistRedundancies() | ||
298 | 360 | ||
299 | await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ]) | 361 | await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ 'videos' ]) |
300 | }) | 362 | }) |
301 | 363 | ||
302 | after(function () { | 364 | after(async function () { |
303 | return cleanServers() | 365 | await cleanupTests(servers) |
304 | }) | 366 | }) |
305 | }) | 367 | }) |
306 | 368 | ||
@@ -310,11 +372,12 @@ describe('Test videos redundancy', function () { | |||
310 | before(function () { | 372 | before(function () { |
311 | this.timeout(120000) | 373 | this.timeout(120000) |
312 | 374 | ||
313 | return runServers(strategy, { min_views: 3 }) | 375 | return flushAndRunServers(strategy, { min_views: 3 }) |
314 | }) | 376 | }) |
315 | 377 | ||
316 | it('Should have 1 webseed on the first video', async function () { | 378 | it('Should have 1 webseed on the first video', async function () { |
317 | await check1WebSeed(strategy) | 379 | await check1WebSeed() |
380 | await check0PlaylistRedundancies() | ||
318 | await checkStatsWith1Webseed(strategy) | 381 | await checkStatsWith1Webseed(strategy) |
319 | }) | 382 | }) |
320 | 383 | ||
@@ -323,18 +386,19 @@ describe('Test videos redundancy', function () { | |||
323 | }) | 386 | }) |
324 | 387 | ||
325 | it('Should still have 1 webseed on the first video', async function () { | 388 | it('Should still have 1 webseed on the first video', async function () { |
326 | this.timeout(40000) | 389 | this.timeout(80000) |
327 | 390 | ||
328 | await waitJobs(servers) | 391 | await waitJobs(servers) |
329 | await wait(15000) | 392 | await wait(15000) |
330 | await waitJobs(servers) | 393 | await waitJobs(servers) |
331 | 394 | ||
332 | await check1WebSeed(strategy) | 395 | await check1WebSeed() |
396 | await check0PlaylistRedundancies() | ||
333 | await checkStatsWith1Webseed(strategy) | 397 | await checkStatsWith1Webseed(strategy) |
334 | }) | 398 | }) |
335 | 399 | ||
336 | it('Should view 2 times the first video to have > min_views config', async function () { | 400 | it('Should view 2 times the first video to have > min_views config', async function () { |
337 | this.timeout(40000) | 401 | this.timeout(80000) |
338 | 402 | ||
339 | await viewVideo(servers[ 0 ].url, video1Server2UUID) | 403 | await viewVideo(servers[ 0 ].url, video1Server2UUID) |
340 | await viewVideo(servers[ 2 ].url, video1Server2UUID) | 404 | await viewVideo(servers[ 2 ].url, video1Server2UUID) |
@@ -344,13 +408,14 @@ describe('Test videos redundancy', function () { | |||
344 | }) | 408 | }) |
345 | 409 | ||
346 | it('Should have 2 webseeds on the first video', async function () { | 410 | it('Should have 2 webseeds on the first video', async function () { |
347 | this.timeout(40000) | 411 | this.timeout(80000) |
348 | 412 | ||
349 | await waitJobs(servers) | 413 | await waitJobs(servers) |
350 | await waitUntilLog(servers[0], 'Duplicated ', 4) | 414 | await waitUntilLog(servers[0], 'Duplicated ', 5) |
351 | await waitJobs(servers) | 415 | await waitJobs(servers) |
352 | 416 | ||
353 | await check2Webseeds(strategy) | 417 | await check2Webseeds() |
418 | await check1PlaylistRedundancies() | ||
354 | await checkStatsWith2Webseed(strategy) | 419 | await checkStatsWith2Webseed(strategy) |
355 | }) | 420 | }) |
356 | 421 | ||
@@ -366,8 +431,8 @@ describe('Test videos redundancy', function () { | |||
366 | } | 431 | } |
367 | }) | 432 | }) |
368 | 433 | ||
369 | after(function () { | 434 | after(async function () { |
370 | return cleanServers() | 435 | await cleanupTests(servers) |
371 | }) | 436 | }) |
372 | }) | 437 | }) |
373 | 438 | ||
@@ -399,13 +464,13 @@ describe('Test videos redundancy', function () { | |||
399 | before(async function () { | 464 | before(async function () { |
400 | this.timeout(120000) | 465 | this.timeout(120000) |
401 | 466 | ||
402 | await runServers(strategy, { min_lifetime: '7 seconds', min_views: 0 }) | 467 | await flushAndRunServers(strategy, { min_lifetime: '7 seconds', min_views: 0 }) |
403 | 468 | ||
404 | await enableRedundancyOnServer1() | 469 | await enableRedundancyOnServer1() |
405 | }) | 470 | }) |
406 | 471 | ||
407 | it('Should still have 2 webseeds after 10 seconds', async function () { | 472 | it('Should still have 2 webseeds after 10 seconds', async function () { |
408 | this.timeout(40000) | 473 | this.timeout(80000) |
409 | 474 | ||
410 | await wait(10000) | 475 | await wait(10000) |
411 | 476 | ||
@@ -420,7 +485,7 @@ describe('Test videos redundancy', function () { | |||
420 | }) | 485 | }) |
421 | 486 | ||
422 | it('Should stop server 1 and expire video redundancy', async function () { | 487 | it('Should stop server 1 and expire video redundancy', async function () { |
423 | this.timeout(40000) | 488 | this.timeout(80000) |
424 | 489 | ||
425 | killallServers([ servers[0] ]) | 490 | killallServers([ servers[0] ]) |
426 | 491 | ||
@@ -429,8 +494,8 @@ describe('Test videos redundancy', function () { | |||
429 | await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2Flocalhost%3A9001') | 494 | await checkNotContains([ servers[1], servers[2] ], 'http%3A%2F%2Flocalhost%3A9001') |
430 | }) | 495 | }) |
431 | 496 | ||
432 | after(function () { | 497 | after(async function () { |
433 | return killallServers([ servers[1], servers[2] ]) | 498 | await cleanupTests(servers) |
434 | }) | 499 | }) |
435 | }) | 500 | }) |
436 | 501 | ||
@@ -441,15 +506,16 @@ describe('Test videos redundancy', function () { | |||
441 | before(async function () { | 506 | before(async function () { |
442 | this.timeout(120000) | 507 | this.timeout(120000) |
443 | 508 | ||
444 | await runServers(strategy, { min_lifetime: '7 seconds', min_views: 0 }) | 509 | await flushAndRunServers(strategy, { min_lifetime: '7 seconds', min_views: 0 }) |
445 | 510 | ||
446 | await enableRedundancyOnServer1() | 511 | await enableRedundancyOnServer1() |
447 | 512 | ||
448 | await waitJobs(servers) | 513 | await waitJobs(servers) |
449 | await waitUntilLog(servers[0], 'Duplicated ', 4) | 514 | await waitUntilLog(servers[0], 'Duplicated ', 5) |
450 | await waitJobs(servers) | 515 | await waitJobs(servers) |
451 | 516 | ||
452 | await check2Webseeds(strategy) | 517 | await check2Webseeds() |
518 | await check1PlaylistRedundancies() | ||
453 | await checkStatsWith2Webseed(strategy) | 519 | await checkStatsWith2Webseed(strategy) |
454 | 520 | ||
455 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 2 server 2' }) | 521 | const res = await uploadVideo(servers[ 1 ].url, servers[ 1 ].accessToken, { name: 'video 2 server 2' }) |
@@ -467,8 +533,10 @@ describe('Test videos redundancy', function () { | |||
467 | await wait(1000) | 533 | await wait(1000) |
468 | 534 | ||
469 | try { | 535 | try { |
470 | await check1WebSeed(strategy, video1Server2UUID) | 536 | await check1WebSeed(video1Server2UUID) |
471 | await check2Webseeds(strategy, video2Server2UUID) | 537 | await check0PlaylistRedundancies(video1Server2UUID) |
538 | await check2Webseeds(video2Server2UUID) | ||
539 | await check1PlaylistRedundancies(video2Server2UUID) | ||
472 | 540 | ||
473 | checked = true | 541 | checked = true |
474 | } catch { | 542 | } catch { |
@@ -477,8 +545,28 @@ describe('Test videos redundancy', function () { | |||
477 | } | 545 | } |
478 | }) | 546 | }) |
479 | 547 | ||
480 | after(function () { | 548 | it('Should disable strategy and remove redundancies', async function () { |
481 | return cleanServers() | 549 | this.timeout(80000) |
550 | |||
551 | await waitJobs(servers) | ||
552 | |||
553 | killallServers([ servers[ 0 ] ]) | ||
554 | await reRunServer(servers[ 0 ], { | ||
555 | redundancy: { | ||
556 | videos: { | ||
557 | check_interval: '1 second', | ||
558 | strategies: [] | ||
559 | } | ||
560 | } | ||
561 | }) | ||
562 | |||
563 | await waitJobs(servers) | ||
564 | |||
565 | await checkVideoFilesWereRemoved(video1Server2UUID, servers[0].serverNumber, [ join('redundancy', 'hls') ]) | ||
566 | }) | ||
567 | |||
568 | after(async function () { | ||
569 | await cleanupTests(servers) | ||
482 | }) | 570 | }) |
483 | }) | 571 | }) |
484 | }) | 572 | }) |
diff --git a/server/tests/api/search/search-activitypub-video-channels.ts b/server/tests/api/search/search-activitypub-video-channels.ts index a411e973b..4d1ceb767 100644 --- a/server/tests/api/search/search-activitypub-video-channels.ts +++ b/server/tests/api/search/search-activitypub-video-channels.ts | |||
@@ -3,7 +3,7 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | addVideoChannel, | 6 | addVideoChannel, cleanupTests, |
7 | createUser, | 7 | createUser, |
8 | deleteVideoChannel, | 8 | deleteVideoChannel, |
9 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
@@ -17,10 +17,10 @@ import { | |||
17 | uploadVideo, | 17 | uploadVideo, |
18 | userLogin, | 18 | userLogin, |
19 | wait | 19 | wait |
20 | } from '../../../../shared/utils' | 20 | } from '../../../../shared/extra-utils' |
21 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 21 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
22 | import { VideoChannel } from '../../../../shared/models/videos' | 22 | import { VideoChannel } from '../../../../shared/models/videos' |
23 | import { searchVideoChannel } from '../../../../shared/utils/search/video-channels' | 23 | import { searchVideoChannel } from '../../../../shared/extra-utils/search/video-channels' |
24 | 24 | ||
25 | const expect = chai.expect | 25 | const expect = chai.expect |
26 | 26 | ||
@@ -33,14 +33,12 @@ describe('Test a ActivityPub video channels search', function () { | |||
33 | before(async function () { | 33 | before(async function () { |
34 | this.timeout(120000) | 34 | this.timeout(120000) |
35 | 35 | ||
36 | await flushTests() | ||
37 | |||
38 | servers = await flushAndRunMultipleServers(2) | 36 | servers = await flushAndRunMultipleServers(2) |
39 | 37 | ||
40 | await setAccessTokensToServers(servers) | 38 | await setAccessTokensToServers(servers) |
41 | 39 | ||
42 | { | 40 | { |
43 | await createUser(servers[0].url, servers[0].accessToken, 'user1_server1', 'password') | 41 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'user1_server1', password: 'password' }) |
44 | const channel = { | 42 | const channel = { |
45 | name: 'channel1_server1', | 43 | name: 'channel1_server1', |
46 | displayName: 'Channel 1 server 1' | 44 | displayName: 'Channel 1 server 1' |
@@ -50,7 +48,7 @@ describe('Test a ActivityPub video channels search', function () { | |||
50 | 48 | ||
51 | { | 49 | { |
52 | const user = { username: 'user1_server2', password: 'password' } | 50 | const user = { username: 'user1_server2', password: 'password' } |
53 | await createUser(servers[1].url, servers[1].accessToken, user.username, user.password) | 51 | await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) |
54 | userServer2Token = await userLogin(servers[1], user) | 52 | userServer2Token = await userLogin(servers[1], user) |
55 | 53 | ||
56 | const channel = { | 54 | const channel = { |
@@ -208,11 +206,6 @@ describe('Test a ActivityPub video channels search', function () { | |||
208 | }) | 206 | }) |
209 | 207 | ||
210 | after(async function () { | 208 | after(async function () { |
211 | killallServers(servers) | 209 | await cleanupTests(servers) |
212 | |||
213 | // Keep the logs if the test failed | ||
214 | if (this['ok']) { | ||
215 | await flushTests() | ||
216 | } | ||
217 | }) | 210 | }) |
218 | }) | 211 | }) |
diff --git a/server/tests/api/search/search-activitypub-videos.ts b/server/tests/api/search/search-activitypub-videos.ts index f881917e7..e039961cb 100644 --- a/server/tests/api/search/search-activitypub-videos.ts +++ b/server/tests/api/search/search-activitypub-videos.ts | |||
@@ -15,9 +15,9 @@ import { | |||
15 | updateVideo, | 15 | updateVideo, |
16 | uploadVideo, | 16 | uploadVideo, |
17 | wait, | 17 | wait, |
18 | searchVideo | 18 | searchVideo, cleanupTests |
19 | } from '../../../../shared/utils' | 19 | } from '../../../../shared/extra-utils' |
20 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 20 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
21 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' | 21 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' |
22 | 22 | ||
23 | const expect = chai.expect | 23 | const expect = chai.expect |
@@ -30,8 +30,6 @@ describe('Test a ActivityPub videos search', function () { | |||
30 | before(async function () { | 30 | before(async function () { |
31 | this.timeout(120000) | 31 | this.timeout(120000) |
32 | 32 | ||
33 | await flushTests() | ||
34 | |||
35 | servers = await flushAndRunMultipleServers(2) | 33 | servers = await flushAndRunMultipleServers(2) |
36 | 34 | ||
37 | await setAccessTokensToServers(servers) | 35 | await setAccessTokensToServers(servers) |
@@ -152,11 +150,6 @@ describe('Test a ActivityPub videos search', function () { | |||
152 | }) | 150 | }) |
153 | 151 | ||
154 | after(async function () { | 152 | after(async function () { |
155 | killallServers(servers) | 153 | await cleanupTests(servers) |
156 | |||
157 | // Keep the logs if the test failed | ||
158 | if (this['ok']) { | ||
159 | await flushTests() | ||
160 | } | ||
161 | }) | 154 | }) |
162 | }) | 155 | }) |
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts index 50da837da..1a086b33a 100644 --- a/server/tests/api/search/search-videos.ts +++ b/server/tests/api/search/search-videos.ts | |||
@@ -6,14 +6,15 @@ import { | |||
6 | advancedVideosSearch, | 6 | advancedVideosSearch, |
7 | flushTests, | 7 | flushTests, |
8 | killallServers, | 8 | killallServers, |
9 | runServer, | 9 | flushAndRunServer, |
10 | searchVideo, | 10 | searchVideo, |
11 | ServerInfo, | 11 | ServerInfo, |
12 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
13 | uploadVideo, | 13 | uploadVideo, |
14 | wait, | 14 | wait, |
15 | immutableAssign | 15 | immutableAssign, |
16 | } from '../../../../shared/utils' | 16 | cleanupTests |
17 | } from '../../../../shared/extra-utils' | ||
17 | 18 | ||
18 | const expect = chai.expect | 19 | const expect = chai.expect |
19 | 20 | ||
@@ -24,9 +25,7 @@ describe('Test a videos search', function () { | |||
24 | before(async function () { | 25 | before(async function () { |
25 | this.timeout(30000) | 26 | this.timeout(30000) |
26 | 27 | ||
27 | await flushTests() | 28 | server = await flushAndRunServer(1) |
28 | |||
29 | server = await runServer(1) | ||
30 | 29 | ||
31 | await setAccessTokensToServers([ server ]) | 30 | await setAccessTokensToServers([ server ]) |
32 | 31 | ||
@@ -60,7 +59,10 @@ describe('Test a videos search', function () { | |||
60 | const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2 '] }) | 59 | const attributes6 = immutableAssign(attributes1, { name: attributes1.name + ' - 6', tags: [ 't1', 't2 '] }) |
61 | await uploadVideo(server.url, server.accessToken, attributes6) | 60 | await uploadVideo(server.url, server.accessToken, attributes6) |
62 | 61 | ||
63 | const attributes7 = immutableAssign(attributes1, { name: attributes1.name + ' - 7' }) | 62 | const attributes7 = immutableAssign(attributes1, { |
63 | name: attributes1.name + ' - 7', | ||
64 | originallyPublishedAt: '2019-02-12T09:58:08.286Z' | ||
65 | }) | ||
64 | await uploadVideo(server.url, server.accessToken, attributes7) | 66 | await uploadVideo(server.url, server.accessToken, attributes7) |
65 | 67 | ||
66 | const attributes8 = immutableAssign(attributes1, { name: attributes1.name + ' - 8', licence: 4 }) | 68 | const attributes8 = immutableAssign(attributes1, { name: attributes1.name + ' - 8', licence: 4 }) |
@@ -343,12 +345,68 @@ describe('Test a videos search', function () { | |||
343 | expect(videos[0].name).to.equal('1111 2222 3333') | 345 | expect(videos[0].name).to.equal('1111 2222 3333') |
344 | }) | 346 | }) |
345 | 347 | ||
346 | after(async function () { | 348 | it('Should search on originally published date', async function () { |
347 | killallServers([ server ]) | 349 | const baseQuery = { |
350 | search: '1111 2222 3333', | ||
351 | languageOneOf: [ 'pl', 'fr' ], | ||
352 | durationMax: 4, | ||
353 | nsfw: 'false' as 'false', | ||
354 | licenceOneOf: [ 1, 4 ] | ||
355 | } | ||
356 | |||
357 | { | ||
358 | const query = immutableAssign(baseQuery, { originallyPublishedStartDate: '2019-02-11T09:58:08.286Z' }) | ||
359 | const res = await advancedVideosSearch(server.url, query) | ||
360 | |||
361 | expect(res.body.total).to.equal(1) | ||
362 | expect(res.body.data[0].name).to.equal('1111 2222 3333 - 7') | ||
363 | } | ||
364 | |||
365 | { | ||
366 | const query = immutableAssign(baseQuery, { originallyPublishedEndDate: '2019-03-11T09:58:08.286Z' }) | ||
367 | const res = await advancedVideosSearch(server.url, query) | ||
368 | |||
369 | expect(res.body.total).to.equal(1) | ||
370 | expect(res.body.data[0].name).to.equal('1111 2222 3333 - 7') | ||
371 | } | ||
348 | 372 | ||
349 | // Keep the logs if the test failed | 373 | { |
350 | if (this['ok']) { | 374 | const query = immutableAssign(baseQuery, { originallyPublishedEndDate: '2019-01-11T09:58:08.286Z' }) |
351 | await flushTests() | 375 | const res = await advancedVideosSearch(server.url, query) |
376 | |||
377 | expect(res.body.total).to.equal(0) | ||
378 | } | ||
379 | |||
380 | { | ||
381 | const query = immutableAssign(baseQuery, { originallyPublishedStartDate: '2019-03-11T09:58:08.286Z' }) | ||
382 | const res = await advancedVideosSearch(server.url, query) | ||
383 | |||
384 | expect(res.body.total).to.equal(0) | ||
385 | } | ||
386 | |||
387 | { | ||
388 | const query = immutableAssign(baseQuery, { | ||
389 | originallyPublishedStartDate: '2019-01-11T09:58:08.286Z', | ||
390 | originallyPublishedEndDate: '2019-01-10T09:58:08.286Z' | ||
391 | }) | ||
392 | const res = await advancedVideosSearch(server.url, query) | ||
393 | |||
394 | expect(res.body.total).to.equal(0) | ||
395 | } | ||
396 | |||
397 | { | ||
398 | const query = immutableAssign(baseQuery, { | ||
399 | originallyPublishedStartDate: '2019-01-11T09:58:08.286Z', | ||
400 | originallyPublishedEndDate: '2019-04-11T09:58:08.286Z' | ||
401 | }) | ||
402 | const res = await advancedVideosSearch(server.url, query) | ||
403 | |||
404 | expect(res.body.total).to.equal(1) | ||
405 | expect(res.body.data[0].name).to.equal('1111 2222 3333 - 7') | ||
352 | } | 406 | } |
353 | }) | 407 | }) |
408 | |||
409 | after(async function () { | ||
410 | await cleanupTests([ server ]) | ||
411 | }) | ||
354 | }) | 412 | }) |
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index bebfc7398..c0d11914b 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -5,18 +5,18 @@ import * as chai from 'chai' | |||
5 | import { About } from '../../../../shared/models/server/about.model' | 5 | import { About } from '../../../../shared/models/server/about.model' |
6 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' | 6 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' |
7 | import { | 7 | import { |
8 | cleanupTests, | ||
8 | deleteCustomConfig, | 9 | deleteCustomConfig, |
10 | flushAndRunServer, | ||
9 | getAbout, | 11 | getAbout, |
10 | killallServers, | ||
11 | reRunServer, | ||
12 | flushTests, | ||
13 | getConfig, | 12 | getConfig, |
14 | getCustomConfig, | 13 | getCustomConfig, |
14 | killallServers, | ||
15 | registerUser, | 15 | registerUser, |
16 | runServer, | 16 | reRunServer, |
17 | setAccessTokensToServers, | 17 | setAccessTokensToServers, |
18 | updateCustomConfig | 18 | updateCustomConfig |
19 | } from '../../../../shared/utils' | 19 | } from '../../../../shared/extra-utils' |
20 | import { ServerConfig } from '../../../../shared/models' | 20 | import { ServerConfig } from '../../../../shared/models' |
21 | 21 | ||
22 | const expect = chai.expect | 22 | const expect = chai.expect |
@@ -30,6 +30,7 @@ function checkInitialConfig (data: CustomConfig) { | |||
30 | expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') | 30 | expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') |
31 | expect(data.instance.terms).to.equal('No terms for now.') | 31 | expect(data.instance.terms).to.equal('No terms for now.') |
32 | expect(data.instance.defaultClientRoute).to.equal('/videos/trending') | 32 | expect(data.instance.defaultClientRoute).to.equal('/videos/trending') |
33 | expect(data.instance.isNSFW).to.be.false | ||
33 | expect(data.instance.defaultNSFWPolicy).to.equal('display') | 34 | expect(data.instance.defaultNSFWPolicy).to.equal('display') |
34 | expect(data.instance.customizations.css).to.be.empty | 35 | expect(data.instance.customizations.css).to.be.empty |
35 | expect(data.instance.customizations.javascript).to.be.empty | 36 | expect(data.instance.customizations.javascript).to.be.empty |
@@ -57,8 +58,14 @@ function checkInitialConfig (data: CustomConfig) { | |||
57 | expect(data.transcoding.resolutions['480p']).to.be.true | 58 | expect(data.transcoding.resolutions['480p']).to.be.true |
58 | expect(data.transcoding.resolutions['720p']).to.be.true | 59 | expect(data.transcoding.resolutions['720p']).to.be.true |
59 | expect(data.transcoding.resolutions['1080p']).to.be.true | 60 | expect(data.transcoding.resolutions['1080p']).to.be.true |
61 | expect(data.transcoding.hls.enabled).to.be.true | ||
62 | |||
60 | expect(data.import.videos.http.enabled).to.be.true | 63 | expect(data.import.videos.http.enabled).to.be.true |
61 | expect(data.import.videos.torrent.enabled).to.be.true | 64 | expect(data.import.videos.torrent.enabled).to.be.true |
65 | expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.false | ||
66 | |||
67 | expect(data.followers.instance.enabled).to.be.true | ||
68 | expect(data.followers.instance.manualApproval).to.be.false | ||
62 | } | 69 | } |
63 | 70 | ||
64 | function checkUpdatedConfig (data: CustomConfig) { | 71 | function checkUpdatedConfig (data: CustomConfig) { |
@@ -67,6 +74,7 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
67 | expect(data.instance.description).to.equal('my super description') | 74 | expect(data.instance.description).to.equal('my super description') |
68 | expect(data.instance.terms).to.equal('my super terms') | 75 | expect(data.instance.terms).to.equal('my super terms') |
69 | expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added') | 76 | expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added') |
77 | expect(data.instance.isNSFW).to.be.true | ||
70 | expect(data.instance.defaultNSFWPolicy).to.equal('blur') | 78 | expect(data.instance.defaultNSFWPolicy).to.equal('blur') |
71 | expect(data.instance.customizations.javascript).to.equal('alert("coucou")') | 79 | expect(data.instance.customizations.javascript).to.equal('alert("coucou")') |
72 | expect(data.instance.customizations.css).to.equal('body { background-color: red; }') | 80 | expect(data.instance.customizations.css).to.equal('body { background-color: red; }') |
@@ -79,7 +87,7 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
79 | 87 | ||
80 | expect(data.signup.enabled).to.be.false | 88 | expect(data.signup.enabled).to.be.false |
81 | expect(data.signup.limit).to.equal(5) | 89 | expect(data.signup.limit).to.equal(5) |
82 | expect(data.signup.requiresEmailVerification).to.be.true | 90 | expect(data.signup.requiresEmailVerification).to.be.false |
83 | 91 | ||
84 | expect(data.admin.email).to.equal('superadmin1@example.com') | 92 | expect(data.admin.email).to.equal('superadmin1@example.com') |
85 | expect(data.contactForm.enabled).to.be.false | 93 | expect(data.contactForm.enabled).to.be.false |
@@ -95,9 +103,14 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
95 | expect(data.transcoding.resolutions['480p']).to.be.true | 103 | expect(data.transcoding.resolutions['480p']).to.be.true |
96 | expect(data.transcoding.resolutions['720p']).to.be.false | 104 | expect(data.transcoding.resolutions['720p']).to.be.false |
97 | expect(data.transcoding.resolutions['1080p']).to.be.false | 105 | expect(data.transcoding.resolutions['1080p']).to.be.false |
106 | expect(data.transcoding.hls.enabled).to.be.false | ||
98 | 107 | ||
99 | expect(data.import.videos.http.enabled).to.be.false | 108 | expect(data.import.videos.http.enabled).to.be.false |
100 | expect(data.import.videos.torrent.enabled).to.be.false | 109 | expect(data.import.videos.torrent.enabled).to.be.false |
110 | expect(data.autoBlacklist.videos.ofUsers.enabled).to.be.true | ||
111 | |||
112 | expect(data.followers.instance.enabled).to.be.false | ||
113 | expect(data.followers.instance.manualApproval).to.be.true | ||
101 | } | 114 | } |
102 | 115 | ||
103 | describe('Test config', function () { | 116 | describe('Test config', function () { |
@@ -105,9 +118,7 @@ describe('Test config', function () { | |||
105 | 118 | ||
106 | before(async function () { | 119 | before(async function () { |
107 | this.timeout(30000) | 120 | this.timeout(30000) |
108 | 121 | server = await flushAndRunServer(1) | |
109 | await flushTests() | ||
110 | server = await runServer(1) | ||
111 | await setAccessTokensToServers([ server ]) | 122 | await setAccessTokensToServers([ server ]) |
112 | }) | 123 | }) |
113 | 124 | ||
@@ -160,6 +171,7 @@ describe('Test config', function () { | |||
160 | description: 'my super description', | 171 | description: 'my super description', |
161 | terms: 'my super terms', | 172 | terms: 'my super terms', |
162 | defaultClientRoute: '/videos/recently-added', | 173 | defaultClientRoute: '/videos/recently-added', |
174 | isNSFW: true, | ||
163 | defaultNSFWPolicy: 'blur' as 'blur', | 175 | defaultNSFWPolicy: 'blur' as 'blur', |
164 | customizations: { | 176 | customizations: { |
165 | javascript: 'alert("coucou")', | 177 | javascript: 'alert("coucou")', |
@@ -183,7 +195,7 @@ describe('Test config', function () { | |||
183 | signup: { | 195 | signup: { |
184 | enabled: false, | 196 | enabled: false, |
185 | limit: 5, | 197 | limit: 5, |
186 | requiresEmailVerification: true | 198 | requiresEmailVerification: false |
187 | }, | 199 | }, |
188 | admin: { | 200 | admin: { |
189 | email: 'superadmin1@example.com' | 201 | email: 'superadmin1@example.com' |
@@ -205,6 +217,9 @@ describe('Test config', function () { | |||
205 | '480p': true, | 217 | '480p': true, |
206 | '720p': false, | 218 | '720p': false, |
207 | '1080p': false | 219 | '1080p': false |
220 | }, | ||
221 | hls: { | ||
222 | enabled: false | ||
208 | } | 223 | } |
209 | }, | 224 | }, |
210 | import: { | 225 | import: { |
@@ -216,6 +231,19 @@ describe('Test config', function () { | |||
216 | enabled: false | 231 | enabled: false |
217 | } | 232 | } |
218 | } | 233 | } |
234 | }, | ||
235 | autoBlacklist: { | ||
236 | videos: { | ||
237 | ofUsers: { | ||
238 | enabled: true | ||
239 | } | ||
240 | } | ||
241 | }, | ||
242 | followers: { | ||
243 | instance: { | ||
244 | enabled: false, | ||
245 | manualApproval: true | ||
246 | } | ||
219 | } | 247 | } |
220 | } | 248 | } |
221 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) | 249 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) |
@@ -273,6 +301,6 @@ describe('Test config', function () { | |||
273 | }) | 301 | }) |
274 | 302 | ||
275 | after(async function () { | 303 | after(async function () { |
276 | killallServers([ server ]) | 304 | await cleanupTests([ server ]) |
277 | }) | 305 | }) |
278 | }) | 306 | }) |
diff --git a/server/tests/api/server/contact-form.ts b/server/tests/api/server/contact-form.ts index 06a2f89b0..ba51198b3 100644 --- a/server/tests/api/server/contact-form.ts +++ b/server/tests/api/server/contact-form.ts | |||
@@ -2,10 +2,18 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, wait } from '../../../../shared/utils' | 5 | import { |
6 | import { MockSmtpServer } from '../../../../shared/utils/miscs/email' | 6 | flushTests, |
7 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 7 | killallServers, |
8 | import { sendContactForm } from '../../../../shared/utils/server/contact-form' | 8 | flushAndRunServer, |
9 | ServerInfo, | ||
10 | setAccessTokensToServers, | ||
11 | wait, | ||
12 | cleanupTests | ||
13 | } from '../../../../shared/extra-utils' | ||
14 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' | ||
15 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
16 | import { sendContactForm } from '../../../../shared/extra-utils/server/contact-form' | ||
9 | 17 | ||
10 | const expect = chai.expect | 18 | const expect = chai.expect |
11 | 19 | ||
@@ -18,14 +26,12 @@ describe('Test contact form', function () { | |||
18 | 26 | ||
19 | await MockSmtpServer.Instance.collectEmails(emails) | 27 | await MockSmtpServer.Instance.collectEmails(emails) |
20 | 28 | ||
21 | await flushTests() | ||
22 | |||
23 | const overrideConfig = { | 29 | const overrideConfig = { |
24 | smtp: { | 30 | smtp: { |
25 | hostname: 'localhost' | 31 | hostname: 'localhost' |
26 | } | 32 | } |
27 | } | 33 | } |
28 | server = await runServer(1, overrideConfig) | 34 | server = await flushAndRunServer(1, overrideConfig) |
29 | await setAccessTokensToServers([ server ]) | 35 | await setAccessTokensToServers([ server ]) |
30 | }) | 36 | }) |
31 | 37 | ||
@@ -82,6 +88,7 @@ describe('Test contact form', function () { | |||
82 | 88 | ||
83 | after(async function () { | 89 | after(async function () { |
84 | MockSmtpServer.Instance.kill() | 90 | MockSmtpServer.Instance.kill() |
85 | killallServers([ server ]) | 91 | |
92 | await cleanupTests([ server ]) | ||
86 | }) | 93 | }) |
87 | }) | 94 | }) |
diff --git a/server/tests/api/server/email.ts b/server/tests/api/server/email.ts index f8f16f54f..bacdf1b1b 100644 --- a/server/tests/api/server/email.ts +++ b/server/tests/api/server/email.ts | |||
@@ -10,7 +10,7 @@ import { | |||
10 | createUser, removeVideoFromBlacklist, | 10 | createUser, removeVideoFromBlacklist, |
11 | reportVideoAbuse, | 11 | reportVideoAbuse, |
12 | resetPassword, | 12 | resetPassword, |
13 | runServer, | 13 | flushAndRunServer, |
14 | unblockUser, | 14 | unblockUser, |
15 | uploadVideo, | 15 | uploadVideo, |
16 | userLogin, | 16 | userLogin, |
@@ -18,10 +18,10 @@ import { | |||
18 | flushTests, | 18 | flushTests, |
19 | killallServers, | 19 | killallServers, |
20 | ServerInfo, | 20 | ServerInfo, |
21 | setAccessTokensToServers | 21 | setAccessTokensToServers, cleanupTests |
22 | } from '../../../../shared/utils' | 22 | } from '../../../../shared/extra-utils' |
23 | import { MockSmtpServer } from '../../../../shared/utils/miscs/email' | 23 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' |
24 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 24 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
25 | 25 | ||
26 | const expect = chai.expect | 26 | const expect = chai.expect |
27 | 27 | ||
@@ -43,18 +43,16 @@ describe('Test emails', function () { | |||
43 | 43 | ||
44 | await MockSmtpServer.Instance.collectEmails(emails) | 44 | await MockSmtpServer.Instance.collectEmails(emails) |
45 | 45 | ||
46 | await flushTests() | ||
47 | |||
48 | const overrideConfig = { | 46 | const overrideConfig = { |
49 | smtp: { | 47 | smtp: { |
50 | hostname: 'localhost' | 48 | hostname: 'localhost' |
51 | } | 49 | } |
52 | } | 50 | } |
53 | server = await runServer(1, overrideConfig) | 51 | server = await flushAndRunServer(1, overrideConfig) |
54 | await setAccessTokensToServers([ server ]) | 52 | await setAccessTokensToServers([ server ]) |
55 | 53 | ||
56 | { | 54 | { |
57 | const res = await createUser(server.url, server.accessToken, user.username, user.password) | 55 | const res = await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
58 | userId = res.body.user.id | 56 | userId = res.body.user.id |
59 | 57 | ||
60 | userAccessToken = await userLogin(server, user) | 58 | userAccessToken = await userLogin(server, user) |
@@ -142,7 +140,8 @@ describe('Test emails', function () { | |||
142 | }) | 140 | }) |
143 | }) | 141 | }) |
144 | 142 | ||
145 | describe('When blocking/unblocking user', async function () { | 143 | describe('When blocking/unblocking user', function () { |
144 | |||
146 | it('Should send the notification email when blocking a user', async function () { | 145 | it('Should send the notification email when blocking a user', async function () { |
147 | this.timeout(10000) | 146 | this.timeout(10000) |
148 | 147 | ||
@@ -259,6 +258,7 @@ describe('Test emails', function () { | |||
259 | 258 | ||
260 | after(async function () { | 259 | after(async function () { |
261 | MockSmtpServer.Instance.kill() | 260 | MockSmtpServer.Instance.kill() |
262 | killallServers([ server ]) | 261 | |
262 | await cleanupTests([ server ]) | ||
263 | }) | 263 | }) |
264 | }) | 264 | }) |
diff --git a/server/tests/api/server/follow-constraints.ts b/server/tests/api/server/follow-constraints.ts index 8bb073c41..4285a9e7a 100644 --- a/server/tests/api/server/follow-constraints.ts +++ b/server/tests/api/server/follow-constraints.ts | |||
@@ -12,11 +12,11 @@ import { | |||
12 | killallServers, | 12 | killallServers, |
13 | ServerInfo, | 13 | ServerInfo, |
14 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
15 | uploadVideo | 15 | uploadVideo, cleanupTests |
16 | } from '../../../../shared/utils' | 16 | } from '../../../../shared/extra-utils' |
17 | import { unfollow } from '../../../../shared/utils/server/follows' | 17 | import { unfollow } from '../../../../shared/extra-utils/server/follows' |
18 | import { userLogin } from '../../../../shared/utils/users/login' | 18 | import { userLogin } from '../../../../shared/extra-utils/users/login' |
19 | import { createUser } from '../../../../shared/utils/users/users' | 19 | import { createUser } from '../../../../shared/extra-utils/users/users' |
20 | 20 | ||
21 | const expect = chai.expect | 21 | const expect = chai.expect |
22 | 22 | ||
@@ -47,7 +47,7 @@ describe('Test follow constraints', function () { | |||
47 | username: 'user1', | 47 | username: 'user1', |
48 | password: 'super_password' | 48 | password: 'super_password' |
49 | } | 49 | } |
50 | await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) | 50 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) |
51 | userAccessToken = await userLogin(servers[0], user) | 51 | userAccessToken = await userLogin(servers[0], user) |
52 | 52 | ||
53 | await doubleFollow(servers[0], servers[1]) | 53 | await doubleFollow(servers[0], servers[1]) |
@@ -220,6 +220,6 @@ describe('Test follow constraints', function () { | |||
220 | }) | 220 | }) |
221 | 221 | ||
222 | after(async function () { | 222 | after(async function () { |
223 | killallServers(servers) | 223 | await cleanupTests(servers) |
224 | }) | 224 | }) |
225 | }) | 225 | }) |
diff --git a/server/tests/api/server/follows-moderation.ts b/server/tests/api/server/follows-moderation.ts new file mode 100644 index 000000000..2a3a4d5c8 --- /dev/null +++ b/server/tests/api/server/follows-moderation.ts | |||
@@ -0,0 +1,195 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | acceptFollower, cleanupTests, | ||
7 | flushAndRunMultipleServers, | ||
8 | killallServers, | ||
9 | ServerInfo, | ||
10 | setAccessTokensToServers, | ||
11 | updateCustomSubConfig | ||
12 | } from '../../../../shared/extra-utils/index' | ||
13 | import { | ||
14 | follow, | ||
15 | getFollowersListPaginationAndSort, | ||
16 | getFollowingListPaginationAndSort, | ||
17 | removeFollower, | ||
18 | rejectFollower | ||
19 | } from '../../../../shared/extra-utils/server/follows' | ||
20 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
21 | import { ActorFollow } from '../../../../shared/models/actors' | ||
22 | |||
23 | const expect = chai.expect | ||
24 | |||
25 | async function checkServer1And2HasFollowers (servers: ServerInfo[], state = 'accepted') { | ||
26 | { | ||
27 | const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt') | ||
28 | expect(res.body.total).to.equal(1) | ||
29 | |||
30 | const follow = res.body.data[0] as ActorFollow | ||
31 | expect(follow.state).to.equal(state) | ||
32 | expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube') | ||
33 | expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube') | ||
34 | } | ||
35 | |||
36 | { | ||
37 | const res = await getFollowersListPaginationAndSort(servers[1].url, 0, 5, 'createdAt') | ||
38 | expect(res.body.total).to.equal(1) | ||
39 | |||
40 | const follow = res.body.data[0] as ActorFollow | ||
41 | expect(follow.state).to.equal(state) | ||
42 | expect(follow.follower.url).to.equal('http://localhost:9001/accounts/peertube') | ||
43 | expect(follow.following.url).to.equal('http://localhost:9002/accounts/peertube') | ||
44 | } | ||
45 | } | ||
46 | |||
47 | async function checkNoFollowers (servers: ServerInfo[]) { | ||
48 | { | ||
49 | const res = await getFollowingListPaginationAndSort(servers[ 0 ].url, 0, 5, 'createdAt') | ||
50 | expect(res.body.total).to.equal(0) | ||
51 | } | ||
52 | |||
53 | { | ||
54 | const res = await getFollowersListPaginationAndSort(servers[ 1 ].url, 0, 5, 'createdAt') | ||
55 | expect(res.body.total).to.equal(0) | ||
56 | } | ||
57 | } | ||
58 | |||
59 | describe('Test follows moderation', function () { | ||
60 | let servers: ServerInfo[] = [] | ||
61 | |||
62 | before(async function () { | ||
63 | this.timeout(30000) | ||
64 | |||
65 | servers = await flushAndRunMultipleServers(3) | ||
66 | |||
67 | // Get the access tokens | ||
68 | await setAccessTokensToServers(servers) | ||
69 | }) | ||
70 | |||
71 | it('Should have server 1 following server 2', async function () { | ||
72 | this.timeout(30000) | ||
73 | |||
74 | await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken) | ||
75 | |||
76 | await waitJobs(servers) | ||
77 | }) | ||
78 | |||
79 | it('Should have correct follows', async function () { | ||
80 | await checkServer1And2HasFollowers(servers) | ||
81 | }) | ||
82 | |||
83 | it('Should remove follower on server 2', async function () { | ||
84 | await removeFollower(servers[1].url, servers[1].accessToken, servers[0]) | ||
85 | |||
86 | await waitJobs(servers) | ||
87 | }) | ||
88 | |||
89 | it('Should not not have follows anymore', async function () { | ||
90 | await checkNoFollowers(servers) | ||
91 | }) | ||
92 | |||
93 | it('Should disable followers on server 2', async function () { | ||
94 | const subConfig = { | ||
95 | followers: { | ||
96 | instance: { | ||
97 | enabled: false, | ||
98 | manualApproval: false | ||
99 | } | ||
100 | } | ||
101 | } | ||
102 | |||
103 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, subConfig) | ||
104 | |||
105 | await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken) | ||
106 | await waitJobs(servers) | ||
107 | |||
108 | await checkNoFollowers(servers) | ||
109 | }) | ||
110 | |||
111 | it('Should re enable followers on server 2', async function () { | ||
112 | const subConfig = { | ||
113 | followers: { | ||
114 | instance: { | ||
115 | enabled: true, | ||
116 | manualApproval: false | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | |||
121 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, subConfig) | ||
122 | |||
123 | await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken) | ||
124 | await waitJobs(servers) | ||
125 | |||
126 | await checkServer1And2HasFollowers(servers) | ||
127 | }) | ||
128 | |||
129 | it('Should manually approve followers', async function () { | ||
130 | this.timeout(20000) | ||
131 | |||
132 | await removeFollower(servers[1].url, servers[1].accessToken, servers[0]) | ||
133 | await waitJobs(servers) | ||
134 | |||
135 | const subConfig = { | ||
136 | followers: { | ||
137 | instance: { | ||
138 | enabled: true, | ||
139 | manualApproval: true | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | |||
144 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, subConfig) | ||
145 | await updateCustomSubConfig(servers[2].url, servers[2].accessToken, subConfig) | ||
146 | |||
147 | await follow(servers[0].url, [ servers[1].url ], servers[0].accessToken) | ||
148 | await waitJobs(servers) | ||
149 | |||
150 | await checkServer1And2HasFollowers(servers, 'pending') | ||
151 | }) | ||
152 | |||
153 | it('Should accept a follower', async function () { | ||
154 | await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@localhost:9001') | ||
155 | await waitJobs(servers) | ||
156 | |||
157 | await checkServer1And2HasFollowers(servers) | ||
158 | }) | ||
159 | |||
160 | it('Should reject another follower', async function () { | ||
161 | this.timeout(20000) | ||
162 | |||
163 | await follow(servers[0].url, [ servers[2].url ], servers[0].accessToken) | ||
164 | await waitJobs(servers) | ||
165 | |||
166 | { | ||
167 | const res = await getFollowingListPaginationAndSort(servers[0].url, 0, 5, 'createdAt') | ||
168 | expect(res.body.total).to.equal(2) | ||
169 | } | ||
170 | |||
171 | { | ||
172 | const res = await getFollowersListPaginationAndSort(servers[1].url, 0, 5, 'createdAt') | ||
173 | expect(res.body.total).to.equal(1) | ||
174 | } | ||
175 | |||
176 | { | ||
177 | const res = await getFollowersListPaginationAndSort(servers[2].url, 0, 5, 'createdAt') | ||
178 | expect(res.body.total).to.equal(1) | ||
179 | } | ||
180 | |||
181 | await rejectFollower(servers[2].url, servers[2].accessToken, 'peertube@localhost:9001') | ||
182 | await waitJobs(servers) | ||
183 | |||
184 | await checkServer1And2HasFollowers(servers) | ||
185 | |||
186 | { | ||
187 | const res = await getFollowersListPaginationAndSort(servers[ 2 ].url, 0, 5, 'createdAt') | ||
188 | expect(res.body.total).to.equal(0) | ||
189 | } | ||
190 | }) | ||
191 | |||
192 | after(async function () { | ||
193 | await cleanupTests(servers) | ||
194 | }) | ||
195 | }) | ||
diff --git a/server/tests/api/server/follows.ts b/server/tests/api/server/follows.ts index b0fc5d293..397093cdb 100644 --- a/server/tests/api/server/follows.ts +++ b/server/tests/api/server/follows.ts | |||
@@ -4,7 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' | 5 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' |
6 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 6 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' |
7 | import { completeVideoCheck } from '../../../../shared/utils' | 7 | import { cleanupTests, completeVideoCheck } from '../../../../shared/extra-utils' |
8 | import { | 8 | import { |
9 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
10 | getVideosList, | 10 | getVideosList, |
@@ -12,26 +12,26 @@ import { | |||
12 | ServerInfo, | 12 | ServerInfo, |
13 | setAccessTokensToServers, | 13 | setAccessTokensToServers, |
14 | uploadVideo | 14 | uploadVideo |
15 | } from '../../../../shared/utils/index' | 15 | } from '../../../../shared/extra-utils/index' |
16 | import { dateIsValid } from '../../../../shared/utils/miscs/miscs' | 16 | import { dateIsValid } from '../../../../shared/extra-utils/miscs/miscs' |
17 | import { | 17 | import { |
18 | follow, | 18 | follow, |
19 | getFollowersListPaginationAndSort, | 19 | getFollowersListPaginationAndSort, |
20 | getFollowingListPaginationAndSort, | 20 | getFollowingListPaginationAndSort, |
21 | unfollow | 21 | unfollow |
22 | } from '../../../../shared/utils/server/follows' | 22 | } from '../../../../shared/extra-utils/server/follows' |
23 | import { expectAccountFollows } from '../../../../shared/utils/users/accounts' | 23 | import { expectAccountFollows } from '../../../../shared/extra-utils/users/accounts' |
24 | import { userLogin } from '../../../../shared/utils/users/login' | 24 | import { userLogin } from '../../../../shared/extra-utils/users/login' |
25 | import { createUser } from '../../../../shared/utils/users/users' | 25 | import { createUser } from '../../../../shared/extra-utils/users/users' |
26 | import { | 26 | import { |
27 | addVideoCommentReply, | 27 | addVideoCommentReply, |
28 | addVideoCommentThread, | 28 | addVideoCommentThread, |
29 | getVideoCommentThreads, | 29 | getVideoCommentThreads, |
30 | getVideoThreadComments | 30 | getVideoThreadComments |
31 | } from '../../../../shared/utils/videos/video-comments' | 31 | } from '../../../../shared/extra-utils/videos/video-comments' |
32 | import { rateVideo } from '../../../../shared/utils/videos/videos' | 32 | import { rateVideo } from '../../../../shared/extra-utils/videos/videos' |
33 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 33 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
34 | import { createVideoCaption, listVideoCaptions, testCaptionFile } from '../../../../shared/utils/videos/video-captions' | 34 | import { createVideoCaption, listVideoCaptions, testCaptionFile } from '../../../../shared/extra-utils/videos/video-captions' |
35 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' | 35 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' |
36 | 36 | ||
37 | const expect = chai.expect | 37 | const expect = chai.expect |
@@ -241,7 +241,7 @@ describe('Test follows', function () { | |||
241 | expect(res.body.data[0].name).to.equal('server3') | 241 | expect(res.body.data[0].name).to.equal('server3') |
242 | }) | 242 | }) |
243 | 243 | ||
244 | describe('Should propagate data on a new following', async function () { | 244 | describe('Should propagate data on a new following', function () { |
245 | let video4: Video | 245 | let video4: Video |
246 | 246 | ||
247 | before(async function () { | 247 | before(async function () { |
@@ -263,7 +263,7 @@ describe('Test follows', function () { | |||
263 | 263 | ||
264 | { | 264 | { |
265 | const user = { username: 'captain', password: 'password' } | 265 | const user = { username: 'captain', password: 'password' } |
266 | await createUser(servers[ 2 ].url, servers[ 2 ].accessToken, user.username, user.password) | 266 | await createUser({ url: servers[ 2 ].url, accessToken: servers[ 2 ].accessToken, username: user.username, password: user.password }) |
267 | const userAccessToken = await userLogin(servers[ 2 ], user) | 267 | const userAccessToken = await userLogin(servers[ 2 ], user) |
268 | 268 | ||
269 | const resVideos = await getVideosList(servers[ 2 ].url) | 269 | const resVideos = await getVideosList(servers[ 2 ].url) |
@@ -348,6 +348,7 @@ describe('Test follows', function () { | |||
348 | }, | 348 | }, |
349 | isLocal, | 349 | isLocal, |
350 | commentsEnabled: true, | 350 | commentsEnabled: true, |
351 | downloadEnabled: true, | ||
351 | duration: 5, | 352 | duration: 5, |
352 | tags: [ 'tag1', 'tag2', 'tag3' ], | 353 | tags: [ 'tag1', 'tag2', 'tag3' ], |
353 | privacy: VideoPrivacy.PUBLIC, | 354 | privacy: VideoPrivacy.PUBLIC, |
@@ -435,6 +436,6 @@ describe('Test follows', function () { | |||
435 | }) | 436 | }) |
436 | 437 | ||
437 | after(async function () { | 438 | after(async function () { |
438 | killallServers(servers) | 439 | await cleanupTests(servers) |
439 | }) | 440 | }) |
440 | }) | 441 | }) |
diff --git a/server/tests/api/server/handle-down.ts b/server/tests/api/server/handle-down.ts index cd7baadad..19010dbc1 100644 --- a/server/tests/api/server/handle-down.ts +++ b/server/tests/api/server/handle-down.ts | |||
@@ -7,6 +7,7 @@ import { VideoPrivacy } from '../../../../shared/models/videos' | |||
7 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 7 | import { VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' |
8 | 8 | ||
9 | import { | 9 | import { |
10 | cleanupTests, | ||
10 | completeVideoCheck, | 11 | completeVideoCheck, |
11 | flushAndRunMultipleServers, | 12 | flushAndRunMultipleServers, |
12 | getVideo, | 13 | getVideo, |
@@ -20,15 +21,15 @@ import { | |||
20 | updateVideo, | 21 | updateVideo, |
21 | uploadVideo, | 22 | uploadVideo, |
22 | wait | 23 | wait |
23 | } from '../../../../shared/utils' | 24 | } from '../../../../shared/extra-utils' |
24 | import { follow, getFollowersListPaginationAndSort } from '../../../../shared/utils/server/follows' | 25 | import { follow, getFollowersListPaginationAndSort } from '../../../../shared/extra-utils/server/follows' |
25 | import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/utils/server/jobs' | 26 | import { getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' |
26 | import { | 27 | import { |
27 | addVideoCommentReply, | 28 | addVideoCommentReply, |
28 | addVideoCommentThread, | 29 | addVideoCommentThread, |
29 | getVideoCommentThreads, | 30 | getVideoCommentThreads, |
30 | getVideoThreadComments | 31 | getVideoThreadComments |
31 | } from '../../../../shared/utils/videos/video-comments' | 32 | } from '../../../../shared/extra-utils/videos/video-comments' |
32 | 33 | ||
33 | const expect = chai.expect | 34 | const expect = chai.expect |
34 | 35 | ||
@@ -76,6 +77,7 @@ describe('Test handle downs', function () { | |||
76 | tags: [ 'tag1p1', 'tag2p1' ], | 77 | tags: [ 'tag1p1', 'tag2p1' ], |
77 | privacy: VideoPrivacy.PUBLIC, | 78 | privacy: VideoPrivacy.PUBLIC, |
78 | commentsEnabled: true, | 79 | commentsEnabled: true, |
80 | downloadEnabled: true, | ||
79 | channel: { | 81 | channel: { |
80 | name: 'root_channel', | 82 | name: 'root_channel', |
81 | displayName: 'Main root channel', | 83 | displayName: 'Main root channel', |
@@ -296,6 +298,6 @@ describe('Test handle downs', function () { | |||
296 | }) | 298 | }) |
297 | 299 | ||
298 | after(async function () { | 300 | after(async function () { |
299 | killallServers(servers) | 301 | await cleanupTests(servers) |
300 | }) | 302 | }) |
301 | }) | 303 | }) |
diff --git a/server/tests/api/server/index.ts b/server/tests/api/server/index.ts index 1f80cc6cf..94c15e0d0 100644 --- a/server/tests/api/server/index.ts +++ b/server/tests/api/server/index.ts | |||
@@ -3,8 +3,10 @@ import './contact-form' | |||
3 | import './email' | 3 | import './email' |
4 | import './follow-constraints' | 4 | import './follow-constraints' |
5 | import './follows' | 5 | import './follows' |
6 | import './follows-moderation' | ||
6 | import './handle-down' | 7 | import './handle-down' |
7 | import './jobs' | 8 | import './jobs' |
9 | import './logs' | ||
8 | import './reverse-proxy' | 10 | import './reverse-proxy' |
9 | import './stats' | 11 | import './stats' |
10 | import './tracker' | 12 | import './tracker' |
diff --git a/server/tests/api/server/jobs.ts b/server/tests/api/server/jobs.ts index 52948b1d6..634654626 100644 --- a/server/tests/api/server/jobs.ts +++ b/server/tests/api/server/jobs.ts | |||
@@ -2,12 +2,12 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/utils/index' | 5 | import { cleanupTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' |
6 | import { doubleFollow } from '../../../../shared/utils/server/follows' | 6 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
7 | import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/utils/server/jobs' | 7 | import { getJobsList, getJobsListPaginationAndSort, waitJobs } from '../../../../shared/extra-utils/server/jobs' |
8 | import { flushAndRunMultipleServers } from '../../../../shared/utils/server/servers' | 8 | import { flushAndRunMultipleServers } from '../../../../shared/extra-utils/server/servers' |
9 | import { uploadVideo } from '../../../../shared/utils/videos/videos' | 9 | import { uploadVideo } from '../../../../shared/extra-utils/videos/videos' |
10 | import { dateIsValid } from '../../../../shared/utils/miscs/miscs' | 10 | import { dateIsValid } from '../../../../shared/extra-utils/miscs/miscs' |
11 | 11 | ||
12 | const expect = chai.expect | 12 | const expect = chai.expect |
13 | 13 | ||
@@ -57,6 +57,6 @@ describe('Test jobs', function () { | |||
57 | }) | 57 | }) |
58 | 58 | ||
59 | after(async function () { | 59 | after(async function () { |
60 | killallServers(servers) | 60 | await cleanupTests(servers) |
61 | }) | 61 | }) |
62 | }) | 62 | }) |
diff --git a/server/tests/api/server/logs.ts b/server/tests/api/server/logs.ts new file mode 100644 index 000000000..3644fa0d3 --- /dev/null +++ b/server/tests/api/server/logs.ts | |||
@@ -0,0 +1,97 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | flushTests, | ||
7 | killallServers, | ||
8 | flushAndRunServer, | ||
9 | ServerInfo, | ||
10 | setAccessTokensToServers, | ||
11 | cleanupTests | ||
12 | } from '../../../../shared/extra-utils/index' | ||
13 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
14 | import { uploadVideo } from '../../../../shared/extra-utils/videos/videos' | ||
15 | import { getLogs } from '../../../../shared/extra-utils/logs/logs' | ||
16 | |||
17 | const expect = chai.expect | ||
18 | |||
19 | describe('Test logs', function () { | ||
20 | let server: ServerInfo | ||
21 | |||
22 | before(async function () { | ||
23 | this.timeout(30000) | ||
24 | |||
25 | server = await flushAndRunServer(1) | ||
26 | await setAccessTokensToServers([ server ]) | ||
27 | }) | ||
28 | |||
29 | it('Should get logs with a start date', async function () { | ||
30 | this.timeout(10000) | ||
31 | |||
32 | await uploadVideo(server.url, server.accessToken, { name: 'video 1' }) | ||
33 | await waitJobs([ server ]) | ||
34 | |||
35 | const now = new Date() | ||
36 | |||
37 | await uploadVideo(server.url, server.accessToken, { name: 'video 2' }) | ||
38 | await waitJobs([ server ]) | ||
39 | |||
40 | const res = await getLogs(server.url, server.accessToken, now) | ||
41 | const logsString = JSON.stringify(res.body) | ||
42 | |||
43 | expect(logsString.includes('video 1')).to.be.false | ||
44 | expect(logsString.includes('video 2')).to.be.true | ||
45 | }) | ||
46 | |||
47 | it('Should get logs with an end date', async function () { | ||
48 | this.timeout(10000) | ||
49 | |||
50 | await uploadVideo(server.url, server.accessToken, { name: 'video 3' }) | ||
51 | await waitJobs([ server ]) | ||
52 | |||
53 | const now1 = new Date() | ||
54 | |||
55 | await uploadVideo(server.url, server.accessToken, { name: 'video 4' }) | ||
56 | await waitJobs([ server ]) | ||
57 | |||
58 | const now2 = new Date() | ||
59 | |||
60 | await uploadVideo(server.url, server.accessToken, { name: 'video 5' }) | ||
61 | await waitJobs([ server ]) | ||
62 | |||
63 | const res = await getLogs(server.url, server.accessToken, now1, now2) | ||
64 | const logsString = JSON.stringify(res.body) | ||
65 | |||
66 | expect(logsString.includes('video 3')).to.be.false | ||
67 | expect(logsString.includes('video 4')).to.be.true | ||
68 | expect(logsString.includes('video 5')).to.be.false | ||
69 | }) | ||
70 | |||
71 | it('Should get filter by level', async function () { | ||
72 | this.timeout(10000) | ||
73 | |||
74 | const now = new Date() | ||
75 | |||
76 | await uploadVideo(server.url, server.accessToken, { name: 'video 6' }) | ||
77 | await waitJobs([ server ]) | ||
78 | |||
79 | { | ||
80 | const res = await getLogs(server.url, server.accessToken, now, undefined, 'info') | ||
81 | const logsString = JSON.stringify(res.body) | ||
82 | |||
83 | expect(logsString.includes('video 6')).to.be.true | ||
84 | } | ||
85 | |||
86 | { | ||
87 | const res = await getLogs(server.url, server.accessToken, now, undefined, 'warn') | ||
88 | const logsString = JSON.stringify(res.body) | ||
89 | |||
90 | expect(logsString.includes('video 6')).to.be.false | ||
91 | } | ||
92 | }) | ||
93 | |||
94 | after(async function () { | ||
95 | await cleanupTests([ server ]) | ||
96 | }) | ||
97 | }) | ||
diff --git a/server/tests/api/server/no-client.ts b/server/tests/api/server/no-client.ts index 3b95ce945..86edeb289 100644 --- a/server/tests/api/server/no-client.ts +++ b/server/tests/api/server/no-client.ts | |||
@@ -1,11 +1,7 @@ | |||
1 | import 'mocha' | 1 | import 'mocha' |
2 | import * as request from 'supertest' | 2 | import * as request from 'supertest' |
3 | import { | 3 | import { ServerInfo } from '../../../../shared/extra-utils' |
4 | flushTests, | 4 | import { cleanupTests, flushAndRunServer } from '../../../../shared/extra-utils/server/servers' |
5 | killallServers, | ||
6 | ServerInfo | ||
7 | } from '../../../../shared/utils' | ||
8 | import { runServer } from '../../../../shared/utils/server/servers' | ||
9 | 5 | ||
10 | describe('Start and stop server without web client routes', function () { | 6 | describe('Start and stop server without web client routes', function () { |
11 | let server: ServerInfo | 7 | let server: ServerInfo |
@@ -13,9 +9,7 @@ describe('Start and stop server without web client routes', function () { | |||
13 | before(async function () { | 9 | before(async function () { |
14 | this.timeout(30000) | 10 | this.timeout(30000) |
15 | 11 | ||
16 | await flushTests() | 12 | server = await flushAndRunServer(1, {}, ['--no-client']) |
17 | |||
18 | server = await runServer(1, {}, ['--no-client']) | ||
19 | }) | 13 | }) |
20 | 14 | ||
21 | it('Should fail getting the client', function () { | 15 | it('Should fail getting the client', function () { |
@@ -26,11 +20,6 @@ describe('Start and stop server without web client routes', function () { | |||
26 | }) | 20 | }) |
27 | 21 | ||
28 | after(async function () { | 22 | after(async function () { |
29 | killallServers([ server ]) | 23 | await cleanupTests([ server ]) |
30 | |||
31 | // Keep the logs if the test failed | ||
32 | if (this['ok']) { | ||
33 | await flushTests() | ||
34 | } | ||
35 | }) | 24 | }) |
36 | }) | 25 | }) |
diff --git a/server/tests/api/server/reverse-proxy.ts b/server/tests/api/server/reverse-proxy.ts index d4c08c346..987538237 100644 --- a/server/tests/api/server/reverse-proxy.ts +++ b/server/tests/api/server/reverse-proxy.ts | |||
@@ -2,28 +2,10 @@ | |||
2 | 2 | ||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { About } from '../../../../shared/models/server/about.model' | 5 | import { cleanupTests, getVideo, uploadVideo, userLogin, viewVideo, wait } from '../../../../shared/extra-utils' |
6 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' | 6 | import { flushAndRunServer, setAccessTokensToServers } from '../../../../shared/extra-utils/index' |
7 | import { | ||
8 | deleteCustomConfig, | ||
9 | getAbout, | ||
10 | getVideo, | ||
11 | killallServers, | ||
12 | login, | ||
13 | reRunServer, | ||
14 | uploadVideo, | ||
15 | userLogin, | ||
16 | viewVideo, | ||
17 | wait | ||
18 | } from '../../../../shared/utils' | ||
19 | const expect = chai.expect | ||
20 | 7 | ||
21 | import { | 8 | const expect = chai.expect |
22 | getConfig, | ||
23 | flushTests, | ||
24 | runServer, | ||
25 | registerUser, getCustomConfig, setAccessTokensToServers, updateCustomConfig | ||
26 | } from '../../../../shared/utils/index' | ||
27 | 9 | ||
28 | describe('Test application behind a reverse proxy', function () { | 10 | describe('Test application behind a reverse proxy', function () { |
29 | let server = null | 11 | let server = null |
@@ -31,9 +13,7 @@ describe('Test application behind a reverse proxy', function () { | |||
31 | 13 | ||
32 | before(async function () { | 14 | before(async function () { |
33 | this.timeout(30000) | 15 | this.timeout(30000) |
34 | 16 | server = await flushAndRunServer(1) | |
35 | await flushTests() | ||
36 | server = await runServer(1) | ||
37 | await setAccessTokensToServers([ server ]) | 17 | await setAccessTokensToServers([ server ]) |
38 | 18 | ||
39 | const { body } = await uploadVideo(server.url, server.accessToken, {}) | 19 | const { body } = await uploadVideo(server.url, server.accessToken, {}) |
@@ -95,7 +75,7 @@ describe('Test application behind a reverse proxy', function () { | |||
95 | it('Should rate limit logins', async function () { | 75 | it('Should rate limit logins', async function () { |
96 | const user = { username: 'root', password: 'fail' } | 76 | const user = { username: 'root', password: 'fail' } |
97 | 77 | ||
98 | for (let i = 0; i < 14; i++) { | 78 | for (let i = 0; i < 19; i++) { |
99 | await userLogin(server, user, 400) | 79 | await userLogin(server, user, 400) |
100 | } | 80 | } |
101 | 81 | ||
@@ -103,6 +83,6 @@ describe('Test application behind a reverse proxy', function () { | |||
103 | }) | 83 | }) |
104 | 84 | ||
105 | after(async function () { | 85 | after(async function () { |
106 | killallServers([ server ]) | 86 | await cleanupTests([ server ]) |
107 | }) | 87 | }) |
108 | }) | 88 | }) |
diff --git a/server/tests/api/server/stats.ts b/server/tests/api/server/stats.ts index aaa6c62f7..a01cd4b38 100644 --- a/server/tests/api/server/stats.ts +++ b/server/tests/api/server/stats.ts | |||
@@ -4,6 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { ServerStats } from '../../../../shared/models/server/server-stats.model' | 5 | import { ServerStats } from '../../../../shared/models/server/server-stats.model' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | createUser, | 8 | createUser, |
8 | doubleFollow, | 9 | doubleFollow, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
@@ -13,11 +14,11 @@ import { | |||
13 | uploadVideo, | 14 | uploadVideo, |
14 | viewVideo, | 15 | viewVideo, |
15 | wait | 16 | wait |
16 | } from '../../../../shared/utils' | 17 | } from '../../../../shared/extra-utils' |
17 | import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index' | 18 | import { flushTests, setAccessTokensToServers } from '../../../../shared/extra-utils/index' |
18 | import { getStats } from '../../../../shared/utils/server/stats' | 19 | import { getStats } from '../../../../shared/extra-utils/server/stats' |
19 | import { addVideoCommentThread } from '../../../../shared/utils/videos/video-comments' | 20 | import { addVideoCommentThread } from '../../../../shared/extra-utils/videos/video-comments' |
20 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 21 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
21 | 22 | ||
22 | const expect = chai.expect | 23 | const expect = chai.expect |
23 | 24 | ||
@@ -26,8 +27,6 @@ describe('Test stats (excluding redundancy)', function () { | |||
26 | 27 | ||
27 | before(async function () { | 28 | before(async function () { |
28 | this.timeout(60000) | 29 | this.timeout(60000) |
29 | |||
30 | await flushTests() | ||
31 | servers = await flushAndRunMultipleServers(3) | 30 | servers = await flushAndRunMultipleServers(3) |
32 | await setAccessTokensToServers(servers) | 31 | await setAccessTokensToServers(servers) |
33 | 32 | ||
@@ -37,7 +36,7 @@ describe('Test stats (excluding redundancy)', function () { | |||
37 | username: 'user1', | 36 | username: 'user1', |
38 | password: 'super_password' | 37 | password: 'super_password' |
39 | } | 38 | } |
40 | await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) | 39 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) |
41 | 40 | ||
42 | const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) | 41 | const resVideo = await uploadVideo(servers[0].url, servers[0].accessToken, { fixture: 'video_short.webm' }) |
43 | const videoUUID = resVideo.body.video.uuid | 42 | const videoUUID = resVideo.body.video.uuid |
@@ -98,6 +97,6 @@ describe('Test stats (excluding redundancy)', function () { | |||
98 | }) | 97 | }) |
99 | 98 | ||
100 | after(async function () { | 99 | after(async function () { |
101 | killallServers(servers) | 100 | await cleanupTests(servers) |
102 | }) | 101 | }) |
103 | }) | 102 | }) |
diff --git a/server/tests/api/server/tracker.ts b/server/tests/api/server/tracker.ts index 25ca00029..9d7eec8ca 100644 --- a/server/tests/api/server/tracker.ts +++ b/server/tests/api/server/tracker.ts | |||
@@ -2,8 +2,16 @@ | |||
2 | 2 | ||
3 | import * as magnetUtil from 'magnet-uri' | 3 | import * as magnetUtil from 'magnet-uri' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { getVideo, killallServers, runServer, ServerInfo, uploadVideo } from '../../../../shared/utils' | 5 | import { |
6 | import { flushTests, setAccessTokensToServers } from '../../../../shared/utils/index' | 6 | cleanupTests, |
7 | flushAndRunServer, | ||
8 | getVideo, | ||
9 | killallServers, | ||
10 | reRunServer, | ||
11 | ServerInfo, | ||
12 | uploadVideo | ||
13 | } from '../../../../shared/extra-utils' | ||
14 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/index' | ||
7 | import { VideoDetails } from '../../../../shared/models/videos' | 15 | import { VideoDetails } from '../../../../shared/models/videos' |
8 | import * as WebTorrent from 'webtorrent' | 16 | import * as WebTorrent from 'webtorrent' |
9 | 17 | ||
@@ -14,9 +22,7 @@ describe('Test tracker', function () { | |||
14 | 22 | ||
15 | before(async function () { | 23 | before(async function () { |
16 | this.timeout(60000) | 24 | this.timeout(60000) |
17 | 25 | server = await flushAndRunServer(1) | |
18 | await flushTests() | ||
19 | server = await runServer(1) | ||
20 | await setAccessTokensToServers([ server ]) | 26 | await setAccessTokensToServers([ server ]) |
21 | 27 | ||
22 | { | 28 | { |
@@ -34,7 +40,7 @@ describe('Test tracker', function () { | |||
34 | } | 40 | } |
35 | }) | 41 | }) |
36 | 42 | ||
37 | it('Should return an error when adding an incorrect infohash', done => { | 43 | it('Should return an error when adding an incorrect infohash', function (done) { |
38 | this.timeout(10000) | 44 | this.timeout(10000) |
39 | const webtorrent = new WebTorrent() | 45 | const webtorrent = new WebTorrent() |
40 | 46 | ||
@@ -49,7 +55,7 @@ describe('Test tracker', function () { | |||
49 | torrent.on('done', () => done(new Error('No error on infohash'))) | 55 | torrent.on('done', () => done(new Error('No error on infohash'))) |
50 | }) | 56 | }) |
51 | 57 | ||
52 | it('Should succeed with the correct infohash', done => { | 58 | it('Should succeed with the correct infohash', function (done) { |
53 | this.timeout(10000) | 59 | this.timeout(10000) |
54 | const webtorrent = new WebTorrent() | 60 | const webtorrent = new WebTorrent() |
55 | 61 | ||
@@ -64,7 +70,27 @@ describe('Test tracker', function () { | |||
64 | torrent.on('done', done) | 70 | torrent.on('done', done) |
65 | }) | 71 | }) |
66 | 72 | ||
67 | after(async function () { | 73 | it('Should disable the tracker', function (done) { |
74 | this.timeout(20000) | ||
75 | |||
68 | killallServers([ server ]) | 76 | killallServers([ server ]) |
77 | reRunServer(server, { tracker: { enabled: false } }) | ||
78 | .then(() => { | ||
79 | const webtorrent = new WebTorrent() | ||
80 | |||
81 | const torrent = webtorrent.add(goodMagnet) | ||
82 | |||
83 | torrent.on('error', done) | ||
84 | torrent.on('warning', warn => { | ||
85 | const message = typeof warn === 'string' ? warn : warn.message | ||
86 | if (message.indexOf('disabled ') !== -1) return done() | ||
87 | }) | ||
88 | |||
89 | torrent.on('done', () => done(new Error('Tracker is enabled'))) | ||
90 | }) | ||
91 | }) | ||
92 | |||
93 | after(async function () { | ||
94 | await cleanupTests([ server ]) | ||
69 | }) | 95 | }) |
70 | }) | 96 | }) |
diff --git a/server/tests/api/users/blocklist.ts b/server/tests/api/users/blocklist.ts index 4bca27a94..fbc57e0ef 100644 --- a/server/tests/api/users/blocklist.ts +++ b/server/tests/api/users/blocklist.ts | |||
@@ -4,6 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { AccountBlock, ServerBlock, Video } from '../../../../shared/index' | 5 | import { AccountBlock, ServerBlock, Video } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | createUser, | 8 | createUser, |
8 | doubleFollow, | 9 | doubleFollow, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
@@ -12,16 +13,16 @@ import { | |||
12 | ServerInfo, | 13 | ServerInfo, |
13 | uploadVideo, | 14 | uploadVideo, |
14 | userLogin | 15 | userLogin |
15 | } from '../../../../shared/utils/index' | 16 | } from '../../../../shared/extra-utils/index' |
16 | import { setAccessTokensToServers } from '../../../../shared/utils/users/login' | 17 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
17 | import { getVideosListWithToken, getVideosList } from '../../../../shared/utils/videos/videos' | 18 | import { getVideosListWithToken, getVideosList } from '../../../../shared/extra-utils/videos/videos' |
18 | import { | 19 | import { |
19 | addVideoCommentReply, | 20 | addVideoCommentReply, |
20 | addVideoCommentThread, | 21 | addVideoCommentThread, |
21 | getVideoCommentThreads, | 22 | getVideoCommentThreads, |
22 | getVideoThreadComments | 23 | getVideoThreadComments |
23 | } from '../../../../shared/utils/videos/video-comments' | 24 | } from '../../../../shared/extra-utils/videos/video-comments' |
24 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 25 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
25 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 26 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' |
26 | import { | 27 | import { |
27 | addAccountToAccountBlocklist, | 28 | addAccountToAccountBlocklist, |
@@ -36,7 +37,7 @@ import { | |||
36 | removeAccountFromServerBlocklist, | 37 | removeAccountFromServerBlocklist, |
37 | removeServerFromAccountBlocklist, | 38 | removeServerFromAccountBlocklist, |
38 | removeServerFromServerBlocklist | 39 | removeServerFromServerBlocklist |
39 | } from '../../../../shared/utils/users/blocklist' | 40 | } from '../../../../shared/extra-utils/users/blocklist' |
40 | 41 | ||
41 | const expect = chai.expect | 42 | const expect = chai.expect |
42 | 43 | ||
@@ -79,14 +80,12 @@ describe('Test blocklist', function () { | |||
79 | before(async function () { | 80 | before(async function () { |
80 | this.timeout(60000) | 81 | this.timeout(60000) |
81 | 82 | ||
82 | await flushTests() | ||
83 | |||
84 | servers = await flushAndRunMultipleServers(2) | 83 | servers = await flushAndRunMultipleServers(2) |
85 | await setAccessTokensToServers(servers) | 84 | await setAccessTokensToServers(servers) |
86 | 85 | ||
87 | { | 86 | { |
88 | const user = { username: 'user1', password: 'password' } | 87 | const user = { username: 'user1', password: 'password' } |
89 | await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) | 88 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) |
90 | 89 | ||
91 | userToken1 = await userLogin(servers[0], user) | 90 | userToken1 = await userLogin(servers[0], user) |
92 | await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' }) | 91 | await uploadVideo(servers[0].url, userToken1, { name: 'video user 1' }) |
@@ -94,14 +93,14 @@ describe('Test blocklist', function () { | |||
94 | 93 | ||
95 | { | 94 | { |
96 | const user = { username: 'moderator', password: 'password' } | 95 | const user = { username: 'moderator', password: 'password' } |
97 | await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) | 96 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) |
98 | 97 | ||
99 | userModeratorToken = await userLogin(servers[0], user) | 98 | userModeratorToken = await userLogin(servers[0], user) |
100 | } | 99 | } |
101 | 100 | ||
102 | { | 101 | { |
103 | const user = { username: 'user2', password: 'password' } | 102 | const user = { username: 'user2', password: 'password' } |
104 | await createUser(servers[1].url, servers[1].accessToken, user.username, user.password) | 103 | await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) |
105 | 104 | ||
106 | userToken2 = await userLogin(servers[1], user) | 105 | userToken2 = await userLogin(servers[1], user) |
107 | await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' }) | 106 | await uploadVideo(servers[1].url, userToken2, { name: 'video user 2' }) |
@@ -501,11 +500,6 @@ describe('Test blocklist', function () { | |||
501 | }) | 500 | }) |
502 | 501 | ||
503 | after(async function () { | 502 | after(async function () { |
504 | killallServers(servers) | 503 | await cleanupTests(servers) |
505 | |||
506 | // Keep the logs if the test failed | ||
507 | if (this[ 'ok' ]) { | ||
508 | await flushTests() | ||
509 | } | ||
510 | }) | 504 | }) |
511 | }) | 505 | }) |
diff --git a/server/tests/api/users/index.ts b/server/tests/api/users/index.ts index 52ba6984e..fcd022429 100644 --- a/server/tests/api/users/index.ts +++ b/server/tests/api/users/index.ts | |||
@@ -1,5 +1,4 @@ | |||
1 | import './users-verification' | 1 | import './users-verification' |
2 | import './user-notifications' | ||
3 | import './blocklist' | 2 | import './blocklist' |
4 | import './user-subscriptions' | 3 | import './user-subscriptions' |
5 | import './users' | 4 | import './users' |
diff --git a/server/tests/api/users/user-subscriptions.ts b/server/tests/api/users/user-subscriptions.ts index 88a7187d6..48811e647 100644 --- a/server/tests/api/users/user-subscriptions.ts +++ b/server/tests/api/users/user-subscriptions.ts | |||
@@ -3,6 +3,7 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
@@ -11,18 +12,18 @@ import { | |||
11 | unfollow, | 12 | unfollow, |
12 | updateVideo, | 13 | updateVideo, |
13 | userLogin | 14 | userLogin |
14 | } from '../../../../shared/utils' | 15 | } from '../../../../shared/extra-utils' |
15 | import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/utils/index' | 16 | import { killallServers, ServerInfo, uploadVideo } from '../../../../shared/extra-utils/index' |
16 | import { setAccessTokensToServers } from '../../../../shared/utils/users/login' | 17 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
17 | import { Video, VideoChannel } from '../../../../shared/models/videos' | 18 | import { Video, VideoChannel } from '../../../../shared/models/videos' |
18 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
19 | import { | 20 | import { |
20 | addUserSubscription, | 21 | addUserSubscription, |
21 | listUserSubscriptions, | 22 | listUserSubscriptions, |
22 | listUserSubscriptionVideos, | 23 | listUserSubscriptionVideos, |
23 | removeUserSubscription, | 24 | removeUserSubscription, |
24 | getUserSubscription, areSubscriptionsExist | 25 | getUserSubscription, areSubscriptionsExist |
25 | } from '../../../../shared/utils/users/user-subscriptions' | 26 | } from '../../../../shared/extra-utils/users/user-subscriptions' |
26 | 27 | ||
27 | const expect = chai.expect | 28 | const expect = chai.expect |
28 | 29 | ||
@@ -45,7 +46,7 @@ describe('Test users subscriptions', function () { | |||
45 | { | 46 | { |
46 | for (const server of servers) { | 47 | for (const server of servers) { |
47 | const user = { username: 'user' + server.serverNumber, password: 'password' } | 48 | const user = { username: 'user' + server.serverNumber, password: 'password' } |
48 | await createUser(server.url, server.accessToken, user.username, user.password) | 49 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
49 | 50 | ||
50 | const accessToken = await userLogin(server, user) | 51 | const accessToken = await userLogin(server, user) |
51 | users.push({ accessToken }) | 52 | users.push({ accessToken }) |
@@ -369,6 +370,6 @@ describe('Test users subscriptions', function () { | |||
369 | }) | 370 | }) |
370 | 371 | ||
371 | after(async function () { | 372 | after(async function () { |
372 | killallServers(servers) | 373 | await cleanupTests(servers) |
373 | }) | 374 | }) |
374 | }) | 375 | }) |
diff --git a/server/tests/api/users/users-multiple-servers.ts b/server/tests/api/users/users-multiple-servers.ts index 006d6cdf0..9a971adb3 100644 --- a/server/tests/api/users/users-multiple-servers.ts +++ b/server/tests/api/users/users-multiple-servers.ts | |||
@@ -4,7 +4,8 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { Account } from '../../../../shared/models/actors' | 5 | import { Account } from '../../../../shared/models/actors' |
6 | import { | 6 | import { |
7 | checkVideoFilesWereRemoved, | 7 | checkTmpIsEmpty, |
8 | checkVideoFilesWereRemoved, cleanupTests, | ||
8 | createUser, | 9 | createUser, |
9 | doubleFollow, | 10 | doubleFollow, |
10 | flushAndRunMultipleServers, | 11 | flushAndRunMultipleServers, |
@@ -13,13 +14,20 @@ import { | |||
13 | removeUser, | 14 | removeUser, |
14 | updateMyUser, | 15 | updateMyUser, |
15 | userLogin | 16 | userLogin |
16 | } from '../../../../shared/utils' | 17 | } from '../../../../shared/extra-utils' |
17 | import { getMyUserInformation, killallServers, ServerInfo, testImage, updateMyAvatar, uploadVideo } from '../../../../shared/utils/index' | 18 | import { |
18 | import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../../../shared/utils/users/accounts' | 19 | getMyUserInformation, |
19 | import { setAccessTokensToServers } from '../../../../shared/utils/users/login' | 20 | killallServers, |
21 | ServerInfo, | ||
22 | testImage, | ||
23 | updateMyAvatar, | ||
24 | uploadVideo | ||
25 | } from '../../../../shared/extra-utils/index' | ||
26 | import { checkActorFilesWereRemoved, getAccount, getAccountsList } from '../../../../shared/extra-utils/users/accounts' | ||
27 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' | ||
20 | import { User } from '../../../../shared/models/users' | 28 | import { User } from '../../../../shared/models/users' |
21 | import { VideoChannel } from '../../../../shared/models/videos' | 29 | import { VideoChannel } from '../../../../shared/models/videos' |
22 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 30 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
23 | 31 | ||
24 | const expect = chai.expect | 32 | const expect = chai.expect |
25 | 33 | ||
@@ -56,7 +64,12 @@ describe('Test users with multiple servers', function () { | |||
56 | username: 'user1', | 64 | username: 'user1', |
57 | password: 'password' | 65 | password: 'password' |
58 | } | 66 | } |
59 | const res = await createUser(servers[ 0 ].url, servers[ 0 ].accessToken, user.username, user.password) | 67 | const res = await createUser({ |
68 | url: servers[ 0 ].url, | ||
69 | accessToken: servers[ 0 ].accessToken, | ||
70 | username: user.username, | ||
71 | password: user.password | ||
72 | }) | ||
60 | userId = res.body.user.id | 73 | userId = res.body.user.id |
61 | userAccessToken = await userLogin(servers[ 0 ], user) | 74 | userAccessToken = await userLogin(servers[ 0 ], user) |
62 | } | 75 | } |
@@ -216,7 +229,13 @@ describe('Test users with multiple servers', function () { | |||
216 | } | 229 | } |
217 | }) | 230 | }) |
218 | 231 | ||
232 | it('Should have an empty tmp directory', async function () { | ||
233 | for (const server of servers) { | ||
234 | await checkTmpIsEmpty(server) | ||
235 | } | ||
236 | }) | ||
237 | |||
219 | after(async function () { | 238 | after(async function () { |
220 | killallServers(servers) | 239 | await cleanupTests(servers) |
221 | }) | 240 | }) |
222 | }) | 241 | }) |
diff --git a/server/tests/api/users/users-verification.ts b/server/tests/api/users/users-verification.ts index babeda2b8..514acf2e7 100644 --- a/server/tests/api/users/users-verification.ts +++ b/server/tests/api/users/users-verification.ts | |||
@@ -4,11 +4,11 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | registerUser, flushTests, getUserInformation, getMyUserInformation, killallServers, | 6 | registerUser, flushTests, getUserInformation, getMyUserInformation, killallServers, |
7 | userLogin, login, runServer, ServerInfo, verifyEmail, updateCustomSubConfig, wait | 7 | userLogin, login, flushAndRunServer, ServerInfo, verifyEmail, updateCustomSubConfig, wait, cleanupTests |
8 | } from '../../../../shared/utils' | 8 | } from '../../../../shared/extra-utils' |
9 | import { setAccessTokensToServers } from '../../../../shared/utils/users/login' | 9 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
10 | import { MockSmtpServer } from '../../../../shared/utils/miscs/email' | 10 | import { MockSmtpServer } from '../../../../shared/extra-utils/miscs/email' |
11 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 11 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
12 | 12 | ||
13 | const expect = chai.expect | 13 | const expect = chai.expect |
14 | 14 | ||
@@ -32,14 +32,12 @@ describe('Test users account verification', function () { | |||
32 | 32 | ||
33 | await MockSmtpServer.Instance.collectEmails(emails) | 33 | await MockSmtpServer.Instance.collectEmails(emails) |
34 | 34 | ||
35 | await flushTests() | ||
36 | |||
37 | const overrideConfig = { | 35 | const overrideConfig = { |
38 | smtp: { | 36 | smtp: { |
39 | hostname: 'localhost' | 37 | hostname: 'localhost' |
40 | } | 38 | } |
41 | } | 39 | } |
42 | server = await runServer(1, overrideConfig) | 40 | server = await flushAndRunServer(1, overrideConfig) |
43 | 41 | ||
44 | await setAccessTokensToServers([ server ]) | 42 | await setAccessTokensToServers([ server ]) |
45 | }) | 43 | }) |
@@ -124,11 +122,7 @@ describe('Test users account verification', function () { | |||
124 | 122 | ||
125 | after(async function () { | 123 | after(async function () { |
126 | MockSmtpServer.Instance.kill() | 124 | MockSmtpServer.Instance.kill() |
127 | killallServers([ server ]) | ||
128 | 125 | ||
129 | // Keep the logs if the test failed | 126 | await cleanupTests([ server ]) |
130 | if (this[ 'ok' ]) { | ||
131 | await flushTests() | ||
132 | } | ||
133 | }) | 127 | }) |
134 | }) | 128 | }) |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index ad98ab1c7..c8e32f3f5 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -2,12 +2,14 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { User, UserRole } from '../../../../shared/index' | 5 | import { User, UserRole, Video } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | blockUser, | 7 | blockUser, |
8 | cleanupTests, | ||
8 | createUser, | 9 | createUser, |
9 | deleteMe, | 10 | deleteMe, |
10 | flushTests, | 11 | flushAndRunServer, |
12 | getAccountRatings, | ||
11 | getBlacklistedVideosList, | 13 | getBlacklistedVideosList, |
12 | getMyUserInformation, | 14 | getMyUserInformation, |
13 | getMyUserVideoQuotaUsed, | 15 | getMyUserVideoQuotaUsed, |
@@ -16,14 +18,12 @@ import { | |||
16 | getUsersList, | 18 | getUsersList, |
17 | getUsersListPaginationAndSort, | 19 | getUsersListPaginationAndSort, |
18 | getVideosList, | 20 | getVideosList, |
19 | killallServers, | ||
20 | login, | 21 | login, |
21 | makePutBodyRequest, | 22 | makePutBodyRequest, |
22 | rateVideo, | 23 | rateVideo, |
23 | registerUser, | 24 | registerUser, |
24 | removeUser, | 25 | removeUser, |
25 | removeVideo, | 26 | removeVideo, |
26 | runServer, | ||
27 | ServerInfo, | 27 | ServerInfo, |
28 | testImage, | 28 | testImage, |
29 | unblockUser, | 29 | unblockUser, |
@@ -32,10 +32,11 @@ import { | |||
32 | updateUser, | 32 | updateUser, |
33 | uploadVideo, | 33 | uploadVideo, |
34 | userLogin | 34 | userLogin |
35 | } from '../../../../shared/utils/index' | 35 | } from '../../../../shared/extra-utils' |
36 | import { follow } from '../../../../shared/utils/server/follows' | 36 | import { follow } from '../../../../shared/extra-utils/server/follows' |
37 | import { setAccessTokensToServers } from '../../../../shared/utils/users/login' | 37 | import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/login' |
38 | import { getMyVideos } from '../../../../shared/utils/videos/videos' | 38 | import { getMyVideos } from '../../../../shared/extra-utils/videos/videos' |
39 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | ||
39 | 40 | ||
40 | const expect = chai.expect | 41 | const expect = chai.expect |
41 | 42 | ||
@@ -52,537 +53,633 @@ describe('Test users', function () { | |||
52 | 53 | ||
53 | before(async function () { | 54 | before(async function () { |
54 | this.timeout(30000) | 55 | this.timeout(30000) |
55 | 56 | server = await flushAndRunServer(1) | |
56 | await flushTests() | ||
57 | server = await runServer(1) | ||
58 | 57 | ||
59 | await setAccessTokensToServers([ server ]) | 58 | await setAccessTokensToServers([ server ]) |
60 | }) | 59 | }) |
61 | 60 | ||
62 | it('Should create a new client') | 61 | describe('OAuth client', function () { |
62 | it('Should create a new client') | ||
63 | 63 | ||
64 | it('Should return the first client') | 64 | it('Should return the first client') |
65 | 65 | ||
66 | it('Should remove the last client') | 66 | it('Should remove the last client') |
67 | 67 | ||
68 | it('Should not login with an invalid client id', async function () { | 68 | it('Should not login with an invalid client id', async function () { |
69 | const client = { id: 'client', secret: server.client.secret } | 69 | const client = { id: 'client', secret: server.client.secret } |
70 | const res = await login(server.url, client, server.user, 400) | 70 | const res = await login(server.url, client, server.user, 400) |
71 | 71 | ||
72 | expect(res.body.error).to.contain('client is invalid') | 72 | expect(res.body.error).to.contain('client is invalid') |
73 | }) | 73 | }) |
74 | 74 | ||
75 | it('Should not login with an invalid client secret', async function () { | 75 | it('Should not login with an invalid client secret', async function () { |
76 | const client = { id: server.client.id, secret: 'coucou' } | 76 | const client = { id: server.client.id, secret: 'coucou' } |
77 | const res = await login(server.url, client, server.user, 400) | 77 | const res = await login(server.url, client, server.user, 400) |
78 | 78 | ||
79 | expect(res.body.error).to.contain('client is invalid') | 79 | expect(res.body.error).to.contain('client is invalid') |
80 | }) | ||
80 | }) | 81 | }) |
81 | 82 | ||
82 | it('Should not login with an invalid username', async function () { | 83 | describe('Login', function () { |
83 | const user = { username: 'captain crochet', password: server.user.password } | ||
84 | const res = await login(server.url, server.client, user, 400) | ||
85 | 84 | ||
86 | expect(res.body.error).to.contain('credentials are invalid') | 85 | it('Should not login with an invalid username', async function () { |
87 | }) | 86 | const user = { username: 'captain crochet', password: server.user.password } |
88 | 87 | const res = await login(server.url, server.client, user, 400) | |
89 | it('Should not login with an invalid password', async function () { | ||
90 | const user = { username: server.user.username, password: 'mew_three' } | ||
91 | const res = await login(server.url, server.client, user, 400) | ||
92 | 88 | ||
93 | expect(res.body.error).to.contain('credentials are invalid') | 89 | expect(res.body.error).to.contain('credentials are invalid') |
94 | }) | 90 | }) |
95 | 91 | ||
96 | it('Should not be able to upload a video', async function () { | 92 | it('Should not login with an invalid password', async function () { |
97 | accessToken = 'my_super_token' | 93 | const user = { username: server.user.username, password: 'mew_three' } |
94 | const res = await login(server.url, server.client, user, 400) | ||
98 | 95 | ||
99 | const videoAttributes = {} | 96 | expect(res.body.error).to.contain('credentials are invalid') |
100 | await uploadVideo(server.url, accessToken, videoAttributes, 401) | 97 | }) |
101 | }) | ||
102 | 98 | ||
103 | it('Should not be able to follow', async function () { | 99 | it('Should not be able to upload a video', async function () { |
104 | accessToken = 'my_super_token' | 100 | accessToken = 'my_super_token' |
105 | await follow(server.url, [ 'http://example.com' ], accessToken, 401) | ||
106 | }) | ||
107 | 101 | ||
108 | it('Should not be able to unfollow') | 102 | const videoAttributes = {} |
103 | await uploadVideo(server.url, accessToken, videoAttributes, 401) | ||
104 | }) | ||
109 | 105 | ||
110 | it('Should be able to login', async function () { | 106 | it('Should not be able to follow', async function () { |
111 | const res = await login(server.url, server.client, server.user, 200) | 107 | accessToken = 'my_super_token' |
108 | await follow(server.url, [ 'http://example.com' ], accessToken, 401) | ||
109 | }) | ||
112 | 110 | ||
113 | accessToken = res.body.access_token | 111 | it('Should not be able to unfollow') |
114 | }) | ||
115 | 112 | ||
116 | it('Should upload the video with the correct token', async function () { | 113 | it('Should be able to login', async function () { |
117 | const videoAttributes = {} | 114 | const res = await login(server.url, server.client, server.user, 200) |
118 | await uploadVideo(server.url, accessToken, videoAttributes) | ||
119 | const res = await getVideosList(server.url) | ||
120 | const video = res.body.data[ 0 ] | ||
121 | 115 | ||
122 | expect(video.account.name).to.equal('root') | 116 | accessToken = res.body.access_token |
123 | videoId = video.id | 117 | }) |
124 | }) | 118 | }) |
125 | 119 | ||
126 | it('Should upload the video again with the correct token', async function () { | 120 | describe('Upload', function () { |
127 | const videoAttributes = {} | ||
128 | await uploadVideo(server.url, accessToken, videoAttributes) | ||
129 | }) | ||
130 | 121 | ||
131 | it('Should retrieve a video rating', async function () { | 122 | it('Should upload the video with the correct token', async function () { |
132 | await rateVideo(server.url, accessToken, videoId, 'like') | 123 | const videoAttributes = {} |
133 | const res = await getMyUserVideoRating(server.url, accessToken, videoId) | 124 | await uploadVideo(server.url, accessToken, videoAttributes) |
134 | const rating = res.body | 125 | const res = await getVideosList(server.url) |
126 | const video = res.body.data[ 0 ] | ||
135 | 127 | ||
136 | expect(rating.videoId).to.equal(videoId) | 128 | expect(video.account.name).to.equal('root') |
137 | expect(rating.rating).to.equal('like') | 129 | videoId = video.id |
138 | }) | 130 | }) |
139 | 131 | ||
140 | it('Should not be able to remove the video with an incorrect token', async function () { | 132 | it('Should upload the video again with the correct token', async function () { |
141 | await removeVideo(server.url, 'bad_token', videoId, 401) | 133 | const videoAttributes = {} |
134 | await uploadVideo(server.url, accessToken, videoAttributes) | ||
135 | }) | ||
142 | }) | 136 | }) |
143 | 137 | ||
144 | it('Should not be able to remove the video with the token of another account') | 138 | describe('Ratings', function () { |
145 | 139 | ||
146 | it('Should be able to remove the video with the correct token', async function () { | 140 | it('Should retrieve a video rating', async function () { |
147 | await removeVideo(server.url, accessToken, videoId) | 141 | await rateVideo(server.url, accessToken, videoId, 'like') |
148 | }) | 142 | const res = await getMyUserVideoRating(server.url, accessToken, videoId) |
143 | const rating = res.body | ||
149 | 144 | ||
150 | it('Should logout (revoke token)') | 145 | expect(rating.videoId).to.equal(videoId) |
151 | 146 | expect(rating.rating).to.equal('like') | |
152 | it('Should not be able to get the user information') | 147 | }) |
153 | 148 | ||
154 | it('Should not be able to upload a video') | 149 | it('Should retrieve ratings list', async function () { |
150 | await rateVideo(server.url, accessToken, videoId, 'like') | ||
155 | 151 | ||
156 | it('Should not be able to remove a video') | 152 | const res = await getAccountRatings(server.url, server.user.username, server.accessToken, null, 200) |
153 | const ratings = res.body | ||
157 | 154 | ||
158 | it('Should not be able to rate a video', async function () { | 155 | expect(ratings.total).to.equal(1) |
159 | const path = '/api/v1/videos/' | 156 | expect(ratings.data[ 0 ].video.id).to.equal(videoId) |
160 | const data = { | 157 | expect(ratings.data[ 0 ].rating).to.equal('like') |
161 | rating: 'likes' | 158 | }) |
162 | } | ||
163 | 159 | ||
164 | const options = { | 160 | it('Should retrieve ratings list by rating type', async function () { |
165 | url: server.url, | 161 | { |
166 | path: path + videoId, | 162 | const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'like') |
167 | token: 'wrong token', | 163 | const ratings = res.body |
168 | fields: data, | 164 | expect(ratings.data.length).to.equal(1) |
169 | statusCodeExpected: 401 | 165 | } |
170 | } | 166 | |
171 | await makePutBodyRequest(options) | 167 | { |
168 | const res = await getAccountRatings(server.url, server.user.username, server.accessToken, 'dislike') | ||
169 | const ratings = res.body | ||
170 | expect(ratings.data.length).to.equal(0) | ||
171 | } | ||
172 | }) | ||
172 | }) | 173 | }) |
173 | 174 | ||
174 | it('Should be able to login again') | 175 | describe('Remove video', function () { |
176 | it('Should not be able to remove the video with an incorrect token', async function () { | ||
177 | await removeVideo(server.url, 'bad_token', videoId, 401) | ||
178 | }) | ||
175 | 179 | ||
176 | it('Should have an expired access token') | 180 | it('Should not be able to remove the video with the token of another account') |
177 | 181 | ||
178 | it('Should refresh the token') | 182 | it('Should be able to remove the video with the correct token', async function () { |
183 | await removeVideo(server.url, accessToken, videoId) | ||
184 | }) | ||
185 | }) | ||
179 | 186 | ||
180 | it('Should be able to upload a video again') | 187 | describe('Logout', function () { |
188 | it('Should logout (revoke token)') | ||
181 | 189 | ||
182 | it('Should be able to create a new user', async function () { | 190 | it('Should not be able to get the user information') |
183 | await createUser(server.url, accessToken, user.username, user.password, 2 * 1024 * 1024) | ||
184 | }) | ||
185 | 191 | ||
186 | it('Should be able to login with this user', async function () { | 192 | it('Should not be able to upload a video') |
187 | accessTokenUser = await userLogin(server, user) | ||
188 | }) | ||
189 | 193 | ||
190 | it('Should be able to get the user information', async function () { | 194 | it('Should not be able to remove a video') |
191 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
192 | const user = res.body | ||
193 | |||
194 | expect(user.username).to.equal('user_1') | ||
195 | expect(user.email).to.equal('user_1@example.com') | ||
196 | expect(user.nsfwPolicy).to.equal('display') | ||
197 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | ||
198 | expect(user.roleLabel).to.equal('User') | ||
199 | expect(user.id).to.be.a('number') | ||
200 | expect(user.account.displayName).to.equal('user_1') | ||
201 | expect(user.account.description).to.be.null | ||
202 | }) | ||
203 | 195 | ||
204 | it('Should be able to upload a video with this user', async function () { | 196 | it('Should not be able to rate a video', async function () { |
205 | this.timeout(5000) | 197 | const path = '/api/v1/videos/' |
198 | const data = { | ||
199 | rating: 'likes' | ||
200 | } | ||
206 | 201 | ||
207 | const videoAttributes = { | 202 | const options = { |
208 | name: 'super user video', | 203 | url: server.url, |
209 | fixture: 'video_short.webm' | 204 | path: path + videoId, |
210 | } | 205 | token: 'wrong token', |
211 | await uploadVideo(server.url, accessTokenUser, videoAttributes) | 206 | fields: data, |
212 | }) | 207 | statusCodeExpected: 401 |
208 | } | ||
209 | await makePutBodyRequest(options) | ||
210 | }) | ||
213 | 211 | ||
214 | it('Should have video quota updated', async function () { | 212 | it('Should be able to login again') |
215 | const res = await getMyUserVideoQuotaUsed(server.url, accessTokenUser) | ||
216 | const data = res.body | ||
217 | 213 | ||
218 | expect(data.videoQuotaUsed).to.equal(218910) | 214 | it('Should have an expired access token') |
219 | 215 | ||
220 | const resUsers = await getUsersList(server.url, server.accessToken) | 216 | it('Should refresh the token') |
221 | 217 | ||
222 | const users: User[] = resUsers.body.data | 218 | it('Should be able to upload a video again') |
223 | const tmpUser = users.find(u => u.username === user.username) | ||
224 | expect(tmpUser.videoQuotaUsed).to.equal(218910) | ||
225 | }) | 219 | }) |
226 | 220 | ||
227 | it('Should be able to list my videos', async function () { | 221 | describe('Creating a user', function () { |
228 | const res = await getMyVideos(server.url, accessTokenUser, 0, 5) | ||
229 | expect(res.body.total).to.equal(1) | ||
230 | 222 | ||
231 | const videos = res.body.data | 223 | it('Should be able to create a new user', async function () { |
232 | expect(videos).to.have.lengthOf(1) | 224 | await createUser({ |
225 | url: server.url, | ||
226 | accessToken: accessToken, | ||
227 | username: user.username, | ||
228 | password: user.password, | ||
229 | videoQuota: 2 * 1024 * 1024, | ||
230 | adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST | ||
231 | }) | ||
232 | }) | ||
233 | 233 | ||
234 | expect(videos[ 0 ].name).to.equal('super user video') | 234 | it('Should be able to login with this user', async function () { |
235 | accessTokenUser = await userLogin(server, user) | ||
236 | }) | ||
237 | |||
238 | it('Should be able to get user information', async function () { | ||
239 | const res1 = await getMyUserInformation(server.url, accessTokenUser) | ||
240 | const userMe: User = res1.body | ||
241 | |||
242 | const res2 = await getUserInformation(server.url, server.accessToken, userMe.id) | ||
243 | const userGet: User = res2.body | ||
244 | |||
245 | for (const user of [ userMe, userGet ]) { | ||
246 | expect(user.username).to.equal('user_1') | ||
247 | expect(user.email).to.equal('user_1@example.com') | ||
248 | expect(user.nsfwPolicy).to.equal('display') | ||
249 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | ||
250 | expect(user.roleLabel).to.equal('User') | ||
251 | expect(user.id).to.be.a('number') | ||
252 | expect(user.account.displayName).to.equal('user_1') | ||
253 | expect(user.account.description).to.be.null | ||
254 | } | ||
255 | |||
256 | expect(userMe.adminFlags).to.be.undefined | ||
257 | expect(userGet.adminFlags).to.equal(UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST) | ||
258 | }) | ||
235 | }) | 259 | }) |
236 | 260 | ||
237 | it('Should list all the users', async function () { | 261 | describe('My videos & quotas', function () { |
238 | const res = await getUsersList(server.url, server.accessToken) | ||
239 | const result = res.body | ||
240 | const total = result.total | ||
241 | const users = result.data | ||
242 | 262 | ||
243 | expect(total).to.equal(2) | 263 | it('Should be able to upload a video with this user', async function () { |
244 | expect(users).to.be.an('array') | 264 | this.timeout(5000) |
245 | expect(users.length).to.equal(2) | ||
246 | 265 | ||
247 | const user = users[ 0 ] | 266 | const videoAttributes = { |
248 | expect(user.username).to.equal('user_1') | 267 | name: 'super user video', |
249 | expect(user.email).to.equal('user_1@example.com') | 268 | fixture: 'video_short.webm' |
250 | expect(user.nsfwPolicy).to.equal('display') | 269 | } |
270 | await uploadVideo(server.url, accessTokenUser, videoAttributes) | ||
271 | }) | ||
251 | 272 | ||
252 | const rootUser = users[ 1 ] | 273 | it('Should have video quota updated', async function () { |
253 | expect(rootUser.username).to.equal('root') | 274 | const res = await getMyUserVideoQuotaUsed(server.url, accessTokenUser) |
254 | expect(rootUser.email).to.equal('admin1@example.com') | 275 | const data = res.body |
255 | expect(user.nsfwPolicy).to.equal('display') | ||
256 | 276 | ||
257 | userId = user.id | 277 | expect(data.videoQuotaUsed).to.equal(218910) |
258 | }) | ||
259 | 278 | ||
260 | it('Should list only the first user by username asc', async function () { | 279 | const resUsers = await getUsersList(server.url, server.accessToken) |
261 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, 'username') | ||
262 | 280 | ||
263 | const result = res.body | 281 | const users: User[] = resUsers.body.data |
264 | const total = result.total | 282 | const tmpUser = users.find(u => u.username === user.username) |
265 | const users = result.data | 283 | expect(tmpUser.videoQuotaUsed).to.equal(218910) |
284 | }) | ||
266 | 285 | ||
267 | expect(total).to.equal(2) | 286 | it('Should be able to list my videos', async function () { |
268 | expect(users.length).to.equal(1) | 287 | const res = await getMyVideos(server.url, accessTokenUser, 0, 5) |
288 | expect(res.body.total).to.equal(1) | ||
289 | |||
290 | const videos = res.body.data | ||
291 | expect(videos).to.have.lengthOf(1) | ||
269 | 292 | ||
270 | const user = users[ 0 ] | 293 | const video: Video = videos[ 0 ] |
271 | expect(user.username).to.equal('root') | 294 | expect(video.name).to.equal('super user video') |
272 | expect(user.email).to.equal('admin1@example.com') | 295 | expect(video.thumbnailPath).to.not.be.null |
273 | expect(user.roleLabel).to.equal('Administrator') | 296 | expect(video.previewPath).to.not.be.null |
274 | expect(user.nsfwPolicy).to.equal('display') | 297 | }) |
275 | }) | 298 | }) |
276 | 299 | ||
277 | it('Should list only the first user by username desc', async function () { | 300 | describe('Users listing', function () { |
278 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-username') | ||
279 | const result = res.body | ||
280 | const total = result.total | ||
281 | const users = result.data | ||
282 | 301 | ||
283 | expect(total).to.equal(2) | 302 | it('Should list all the users', async function () { |
284 | expect(users.length).to.equal(1) | 303 | const res = await getUsersList(server.url, server.accessToken) |
304 | const result = res.body | ||
305 | const total = result.total | ||
306 | const users = result.data | ||
285 | 307 | ||
286 | const user = users[ 0 ] | 308 | expect(total).to.equal(2) |
287 | expect(user.username).to.equal('user_1') | 309 | expect(users).to.be.an('array') |
288 | expect(user.email).to.equal('user_1@example.com') | 310 | expect(users.length).to.equal(2) |
289 | expect(user.nsfwPolicy).to.equal('display') | ||
290 | }) | ||
291 | 311 | ||
292 | it('Should list only the second user by createdAt desc', async function () { | 312 | const user = users[ 0 ] |
293 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-createdAt') | 313 | expect(user.username).to.equal('user_1') |
294 | const result = res.body | 314 | expect(user.email).to.equal('user_1@example.com') |
295 | const total = result.total | 315 | expect(user.nsfwPolicy).to.equal('display') |
296 | const users = result.data | ||
297 | 316 | ||
298 | expect(total).to.equal(2) | 317 | const rootUser = users[ 1 ] |
299 | expect(users.length).to.equal(1) | 318 | expect(rootUser.username).to.equal('root') |
319 | expect(rootUser.email).to.equal('admin1@example.com') | ||
320 | expect(user.nsfwPolicy).to.equal('display') | ||
300 | 321 | ||
301 | const user = users[ 0 ] | 322 | userId = user.id |
302 | expect(user.username).to.equal('user_1') | 323 | }) |
303 | expect(user.email).to.equal('user_1@example.com') | ||
304 | expect(user.nsfwPolicy).to.equal('display') | ||
305 | }) | ||
306 | 324 | ||
307 | it('Should list all the users by createdAt asc', async function () { | 325 | it('Should list only the first user by username asc', async function () { |
308 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt') | 326 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, 'username') |
309 | const result = res.body | ||
310 | const total = result.total | ||
311 | const users = result.data | ||
312 | 327 | ||
313 | expect(total).to.equal(2) | 328 | const result = res.body |
314 | expect(users.length).to.equal(2) | 329 | const total = result.total |
330 | const users = result.data | ||
315 | 331 | ||
316 | expect(users[ 0 ].username).to.equal('root') | 332 | expect(total).to.equal(2) |
317 | expect(users[ 0 ].email).to.equal('admin1@example.com') | 333 | expect(users.length).to.equal(1) |
318 | expect(users[ 0 ].nsfwPolicy).to.equal('display') | ||
319 | 334 | ||
320 | expect(users[ 1 ].username).to.equal('user_1') | 335 | const user = users[ 0 ] |
321 | expect(users[ 1 ].email).to.equal('user_1@example.com') | 336 | expect(user.username).to.equal('root') |
322 | expect(users[ 1 ].nsfwPolicy).to.equal('display') | 337 | expect(user.email).to.equal('admin1@example.com') |
323 | }) | 338 | expect(user.roleLabel).to.equal('Administrator') |
339 | expect(user.nsfwPolicy).to.equal('display') | ||
340 | }) | ||
324 | 341 | ||
325 | it('Should search user by username', async function () { | 342 | it('Should list only the first user by username desc', async function () { |
326 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'oot') | 343 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-username') |
327 | const users = res.body.data as User[] | 344 | const result = res.body |
345 | const total = result.total | ||
346 | const users = result.data | ||
328 | 347 | ||
329 | expect(res.body.total).to.equal(1) | 348 | expect(total).to.equal(2) |
330 | expect(users.length).to.equal(1) | 349 | expect(users.length).to.equal(1) |
331 | 350 | ||
332 | expect(users[ 0 ].username).to.equal('root') | 351 | const user = users[ 0 ] |
333 | }) | 352 | expect(user.username).to.equal('user_1') |
353 | expect(user.email).to.equal('user_1@example.com') | ||
354 | expect(user.nsfwPolicy).to.equal('display') | ||
355 | }) | ||
334 | 356 | ||
335 | it('Should search user by email', async function () { | 357 | it('Should list only the second user by createdAt desc', async function () { |
336 | { | 358 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 1, '-createdAt') |
337 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'r_1@exam') | 359 | const result = res.body |
338 | const users = res.body.data as User[] | 360 | const total = result.total |
361 | const users = result.data | ||
339 | 362 | ||
340 | expect(res.body.total).to.equal(1) | 363 | expect(total).to.equal(2) |
341 | expect(users.length).to.equal(1) | 364 | expect(users.length).to.equal(1) |
342 | 365 | ||
343 | expect(users[ 0 ].username).to.equal('user_1') | 366 | const user = users[ 0 ] |
344 | expect(users[ 0 ].email).to.equal('user_1@example.com') | 367 | expect(user.username).to.equal('user_1') |
345 | } | 368 | expect(user.email).to.equal('user_1@example.com') |
369 | expect(user.nsfwPolicy).to.equal('display') | ||
370 | }) | ||
346 | 371 | ||
347 | { | 372 | it('Should list all the users by createdAt asc', async function () { |
348 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'example') | 373 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt') |
349 | const users = res.body.data as User[] | 374 | const result = res.body |
375 | const total = result.total | ||
376 | const users = result.data | ||
350 | 377 | ||
351 | expect(res.body.total).to.equal(2) | 378 | expect(total).to.equal(2) |
352 | expect(users.length).to.equal(2) | 379 | expect(users.length).to.equal(2) |
353 | 380 | ||
354 | expect(users[ 0 ].username).to.equal('root') | 381 | expect(users[ 0 ].username).to.equal('root') |
355 | expect(users[ 1 ].username).to.equal('user_1') | 382 | expect(users[ 0 ].email).to.equal('admin1@example.com') |
356 | } | 383 | expect(users[ 0 ].nsfwPolicy).to.equal('display') |
357 | }) | ||
358 | 384 | ||
359 | it('Should update my password', async function () { | 385 | expect(users[ 1 ].username).to.equal('user_1') |
360 | await updateMyUser({ | 386 | expect(users[ 1 ].email).to.equal('user_1@example.com') |
361 | url: server.url, | 387 | expect(users[ 1 ].nsfwPolicy).to.equal('display') |
362 | accessToken: accessTokenUser, | ||
363 | currentPassword: 'super password', | ||
364 | newPassword: 'new password' | ||
365 | }) | 388 | }) |
366 | user.password = 'new password' | ||
367 | 389 | ||
368 | await userLogin(server, user, 200) | 390 | it('Should search user by username', async function () { |
369 | }) | 391 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'oot') |
392 | const users = res.body.data as User[] | ||
393 | |||
394 | expect(res.body.total).to.equal(1) | ||
395 | expect(users.length).to.equal(1) | ||
370 | 396 | ||
371 | it('Should be able to change the NSFW display attribute', async function () { | 397 | expect(users[ 0 ].username).to.equal('root') |
372 | await updateMyUser({ | ||
373 | url: server.url, | ||
374 | accessToken: accessTokenUser, | ||
375 | nsfwPolicy: 'do_not_list' | ||
376 | }) | 398 | }) |
377 | 399 | ||
378 | const res = await getMyUserInformation(server.url, accessTokenUser) | 400 | it('Should search user by email', async function () { |
379 | const user = res.body | 401 | { |
402 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'r_1@exam') | ||
403 | const users = res.body.data as User[] | ||
380 | 404 | ||
381 | expect(user.username).to.equal('user_1') | 405 | expect(res.body.total).to.equal(1) |
382 | expect(user.email).to.equal('user_1@example.com') | 406 | expect(users.length).to.equal(1) |
383 | expect(user.nsfwPolicy).to.equal('do_not_list') | ||
384 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | ||
385 | expect(user.id).to.be.a('number') | ||
386 | expect(user.account.displayName).to.equal('user_1') | ||
387 | expect(user.account.description).to.be.null | ||
388 | }) | ||
389 | 407 | ||
390 | it('Should be able to change the autoPlayVideo attribute', async function () { | 408 | expect(users[ 0 ].username).to.equal('user_1') |
391 | await updateMyUser({ | 409 | expect(users[ 0 ].email).to.equal('user_1@example.com') |
392 | url: server.url, | 410 | } |
393 | accessToken: accessTokenUser, | ||
394 | autoPlayVideo: false | ||
395 | }) | ||
396 | 411 | ||
397 | const res = await getMyUserInformation(server.url, accessTokenUser) | 412 | { |
398 | const user = res.body | 413 | const res = await getUsersListPaginationAndSort(server.url, server.accessToken, 0, 2, 'createdAt', 'example') |
414 | const users = res.body.data as User[] | ||
399 | 415 | ||
400 | expect(user.autoPlayVideo).to.be.false | 416 | expect(res.body.total).to.equal(2) |
417 | expect(users.length).to.equal(2) | ||
418 | |||
419 | expect(users[ 0 ].username).to.equal('root') | ||
420 | expect(users[ 1 ].username).to.equal('user_1') | ||
421 | } | ||
422 | }) | ||
401 | }) | 423 | }) |
402 | 424 | ||
403 | it('Should be able to change the email display attribute', async function () { | 425 | describe('Update my account', function () { |
404 | await updateMyUser({ | 426 | it('Should update my password', async function () { |
405 | url: server.url, | 427 | await updateMyUser({ |
406 | accessToken: accessTokenUser, | 428 | url: server.url, |
407 | email: 'updated@example.com' | 429 | accessToken: accessTokenUser, |
430 | currentPassword: 'super password', | ||
431 | newPassword: 'new password' | ||
432 | }) | ||
433 | user.password = 'new password' | ||
434 | |||
435 | await userLogin(server, user, 200) | ||
408 | }) | 436 | }) |
409 | 437 | ||
410 | const res = await getMyUserInformation(server.url, accessTokenUser) | 438 | it('Should be able to change the NSFW display attribute', async function () { |
411 | const user = res.body | 439 | await updateMyUser({ |
440 | url: server.url, | ||
441 | accessToken: accessTokenUser, | ||
442 | nsfwPolicy: 'do_not_list' | ||
443 | }) | ||
444 | |||
445 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
446 | const user = res.body | ||
447 | |||
448 | expect(user.username).to.equal('user_1') | ||
449 | expect(user.email).to.equal('user_1@example.com') | ||
450 | expect(user.nsfwPolicy).to.equal('do_not_list') | ||
451 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | ||
452 | expect(user.id).to.be.a('number') | ||
453 | expect(user.account.displayName).to.equal('user_1') | ||
454 | expect(user.account.description).to.be.null | ||
455 | }) | ||
412 | 456 | ||
413 | expect(user.username).to.equal('user_1') | 457 | it('Should be able to change the autoPlayVideo attribute', async function () { |
414 | expect(user.email).to.equal('updated@example.com') | 458 | await updateMyUser({ |
415 | expect(user.nsfwPolicy).to.equal('do_not_list') | 459 | url: server.url, |
416 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | 460 | accessToken: accessTokenUser, |
417 | expect(user.id).to.be.a('number') | 461 | autoPlayVideo: false |
418 | expect(user.account.displayName).to.equal('user_1') | 462 | }) |
419 | expect(user.account.description).to.be.null | 463 | |
420 | }) | 464 | const res = await getMyUserInformation(server.url, accessTokenUser) |
465 | const user = res.body | ||
421 | 466 | ||
422 | it('Should be able to update my avatar', async function () { | 467 | expect(user.autoPlayVideo).to.be.false |
423 | const fixture = 'avatar.png' | 468 | }) |
424 | 469 | ||
425 | await updateMyAvatar({ | 470 | it('Should be able to change the email display attribute', async function () { |
426 | url: server.url, | 471 | await updateMyUser({ |
427 | accessToken: accessTokenUser, | 472 | url: server.url, |
428 | fixture | 473 | accessToken: accessTokenUser, |
474 | email: 'updated@example.com' | ||
475 | }) | ||
476 | |||
477 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
478 | const user = res.body | ||
479 | |||
480 | expect(user.username).to.equal('user_1') | ||
481 | expect(user.email).to.equal('updated@example.com') | ||
482 | expect(user.nsfwPolicy).to.equal('do_not_list') | ||
483 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | ||
484 | expect(user.id).to.be.a('number') | ||
485 | expect(user.account.displayName).to.equal('user_1') | ||
486 | expect(user.account.description).to.be.null | ||
429 | }) | 487 | }) |
430 | 488 | ||
431 | const res = await getMyUserInformation(server.url, accessTokenUser) | 489 | it('Should be able to update my avatar', async function () { |
432 | const user = res.body | 490 | const fixture = 'avatar.png' |
433 | 491 | ||
434 | await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.png') | 492 | await updateMyAvatar({ |
435 | }) | 493 | url: server.url, |
494 | accessToken: accessTokenUser, | ||
495 | fixture | ||
496 | }) | ||
497 | |||
498 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
499 | const user = res.body | ||
436 | 500 | ||
437 | it('Should be able to update my display name', async function () { | 501 | await testImage(server.url, 'avatar-resized', user.account.avatar.path, '.png') |
438 | await updateMyUser({ | ||
439 | url: server.url, | ||
440 | accessToken: accessTokenUser, | ||
441 | displayName: 'new display name' | ||
442 | }) | 502 | }) |
443 | 503 | ||
444 | const res = await getMyUserInformation(server.url, accessTokenUser) | 504 | it('Should be able to update my display name', async function () { |
445 | const user = res.body | 505 | await updateMyUser({ |
506 | url: server.url, | ||
507 | accessToken: accessTokenUser, | ||
508 | displayName: 'new display name' | ||
509 | }) | ||
510 | |||
511 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
512 | const user = res.body | ||
513 | |||
514 | expect(user.username).to.equal('user_1') | ||
515 | expect(user.email).to.equal('updated@example.com') | ||
516 | expect(user.nsfwPolicy).to.equal('do_not_list') | ||
517 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | ||
518 | expect(user.id).to.be.a('number') | ||
519 | expect(user.account.displayName).to.equal('new display name') | ||
520 | expect(user.account.description).to.be.null | ||
521 | }) | ||
446 | 522 | ||
447 | expect(user.username).to.equal('user_1') | 523 | it('Should be able to update my description', async function () { |
448 | expect(user.email).to.equal('updated@example.com') | 524 | await updateMyUser({ |
449 | expect(user.nsfwPolicy).to.equal('do_not_list') | 525 | url: server.url, |
450 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | 526 | accessToken: accessTokenUser, |
451 | expect(user.id).to.be.a('number') | 527 | description: 'my super description updated' |
452 | expect(user.account.displayName).to.equal('new display name') | 528 | }) |
453 | expect(user.account.description).to.be.null | 529 | |
530 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
531 | const user = res.body | ||
532 | |||
533 | expect(user.username).to.equal('user_1') | ||
534 | expect(user.email).to.equal('updated@example.com') | ||
535 | expect(user.nsfwPolicy).to.equal('do_not_list') | ||
536 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | ||
537 | expect(user.id).to.be.a('number') | ||
538 | expect(user.account.displayName).to.equal('new display name') | ||
539 | expect(user.account.description).to.equal('my super description updated') | ||
540 | }) | ||
454 | }) | 541 | }) |
455 | 542 | ||
456 | it('Should be able to update my description', async function () { | 543 | describe('Updating another user', function () { |
457 | await updateMyUser({ | 544 | |
458 | url: server.url, | 545 | it('Should be able to update another user', async function () { |
459 | accessToken: accessTokenUser, | 546 | await updateUser({ |
460 | description: 'my super description updated' | 547 | url: server.url, |
548 | userId, | ||
549 | accessToken, | ||
550 | email: 'updated2@example.com', | ||
551 | emailVerified: true, | ||
552 | videoQuota: 42, | ||
553 | role: UserRole.MODERATOR, | ||
554 | adminFlags: UserAdminFlag.NONE | ||
555 | }) | ||
556 | |||
557 | const res = await getUserInformation(server.url, accessToken, userId) | ||
558 | const user = res.body | ||
559 | |||
560 | expect(user.username).to.equal('user_1') | ||
561 | expect(user.email).to.equal('updated2@example.com') | ||
562 | expect(user.emailVerified).to.be.true | ||
563 | expect(user.nsfwPolicy).to.equal('do_not_list') | ||
564 | expect(user.videoQuota).to.equal(42) | ||
565 | expect(user.roleLabel).to.equal('Moderator') | ||
566 | expect(user.id).to.be.a('number') | ||
567 | expect(user.adminFlags).to.equal(UserAdminFlag.NONE) | ||
461 | }) | 568 | }) |
462 | 569 | ||
463 | const res = await getMyUserInformation(server.url, accessTokenUser) | 570 | it('Should have removed the user token', async function () { |
464 | const user = res.body | 571 | await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401) |
465 | 572 | ||
466 | expect(user.username).to.equal('user_1') | 573 | accessTokenUser = await userLogin(server, user) |
467 | expect(user.email).to.equal('updated@example.com') | 574 | }) |
468 | expect(user.nsfwPolicy).to.equal('do_not_list') | ||
469 | expect(user.videoQuota).to.equal(2 * 1024 * 1024) | ||
470 | expect(user.id).to.be.a('number') | ||
471 | expect(user.account.displayName).to.equal('new display name') | ||
472 | expect(user.account.description).to.equal('my super description updated') | ||
473 | }) | ||
474 | 575 | ||
475 | it('Should be able to update another user', async function () { | 576 | it('Should be able to update another user password', async function () { |
476 | await updateUser({ | 577 | await updateUser({ |
477 | url: server.url, | 578 | url: server.url, |
478 | userId, | 579 | userId, |
479 | accessToken, | 580 | accessToken, |
480 | email: 'updated2@example.com', | 581 | password: 'password updated' |
481 | emailVerified: true, | 582 | }) |
482 | videoQuota: 42, | ||
483 | role: UserRole.MODERATOR | ||
484 | }) | ||
485 | |||
486 | const res = await getUserInformation(server.url, accessToken, userId) | ||
487 | const user = res.body | ||
488 | |||
489 | expect(user.username).to.equal('user_1') | ||
490 | expect(user.email).to.equal('updated2@example.com') | ||
491 | expect(user.emailVerified).to.be.true | ||
492 | expect(user.nsfwPolicy).to.equal('do_not_list') | ||
493 | expect(user.videoQuota).to.equal(42) | ||
494 | expect(user.roleLabel).to.equal('Moderator') | ||
495 | expect(user.id).to.be.a('number') | ||
496 | }) | ||
497 | 583 | ||
498 | it('Should have removed the user token', async function () { | 584 | await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401) |
499 | await getMyUserVideoQuotaUsed(server.url, accessTokenUser, 401) | ||
500 | 585 | ||
501 | accessTokenUser = await userLogin(server, user) | 586 | await userLogin(server, user, 400) |
502 | }) | ||
503 | 587 | ||
504 | it('Should be able to list video blacklist by a moderator', async function () { | 588 | user.password = 'password updated' |
505 | await getBlacklistedVideosList(server.url, accessTokenUser) | 589 | accessTokenUser = await userLogin(server, user) |
590 | }) | ||
506 | }) | 591 | }) |
507 | 592 | ||
508 | it('Should be able to remove this user', async function () { | 593 | describe('Video blacklists', function () { |
509 | await removeUser(server.url, userId, accessToken) | 594 | it('Should be able to list video blacklist by a moderator', async function () { |
595 | await getBlacklistedVideosList({ url: server.url, token: accessTokenUser }) | ||
596 | }) | ||
510 | }) | 597 | }) |
511 | 598 | ||
512 | it('Should not be able to login with this user', async function () { | 599 | describe('Remove a user', function () { |
513 | await userLogin(server, user, 400) | 600 | it('Should be able to remove this user', async function () { |
514 | }) | 601 | await removeUser(server.url, userId, accessToken) |
602 | }) | ||
515 | 603 | ||
516 | it('Should not have videos of this user', async function () { | 604 | it('Should not be able to login with this user', async function () { |
517 | const res = await getVideosList(server.url) | 605 | await userLogin(server, user, 400) |
606 | }) | ||
518 | 607 | ||
519 | expect(res.body.total).to.equal(1) | 608 | it('Should not have videos of this user', async function () { |
609 | const res = await getVideosList(server.url) | ||
520 | 610 | ||
521 | const video = res.body.data[ 0 ] | 611 | expect(res.body.total).to.equal(1) |
522 | expect(video.account.name).to.equal('root') | ||
523 | }) | ||
524 | 612 | ||
525 | it('Should register a new user', async function () { | 613 | const video = res.body.data[ 0 ] |
526 | await registerUser(server.url, 'user_15', 'my super password') | 614 | expect(video.account.name).to.equal('root') |
615 | }) | ||
527 | }) | 616 | }) |
528 | 617 | ||
529 | it('Should be able to login with this registered user', async function () { | 618 | describe('Registering a new user', function () { |
530 | const user15 = { | 619 | it('Should register a new user', async function () { |
531 | username: 'user_15', | 620 | await registerUser(server.url, 'user_15', 'my super password') |
532 | password: 'my super password' | 621 | }) |
533 | } | ||
534 | 622 | ||
535 | accessToken = await userLogin(server, user15) | 623 | it('Should be able to login with this registered user', async function () { |
536 | }) | 624 | const user15 = { |
625 | username: 'user_15', | ||
626 | password: 'my super password' | ||
627 | } | ||
537 | 628 | ||
538 | it('Should have the correct video quota', async function () { | 629 | accessToken = await userLogin(server, user15) |
539 | const res = await getMyUserInformation(server.url, accessToken) | 630 | }) |
540 | const user = res.body | ||
541 | 631 | ||
542 | expect(user.videoQuota).to.equal(5 * 1024 * 1024) | 632 | it('Should have the correct video quota', async function () { |
543 | }) | 633 | const res = await getMyUserInformation(server.url, accessToken) |
634 | const user = res.body | ||
544 | 635 | ||
545 | it('Should remove me', async function () { | 636 | expect(user.videoQuota).to.equal(5 * 1024 * 1024) |
546 | { | 637 | }) |
547 | const res = await getUsersList(server.url, server.accessToken) | ||
548 | expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined | ||
549 | } | ||
550 | 638 | ||
551 | await deleteMe(server.url, accessToken) | 639 | it('Should remove me', async function () { |
640 | { | ||
641 | const res = await getUsersList(server.url, server.accessToken) | ||
642 | expect(res.body.data.find(u => u.username === 'user_15')).to.not.be.undefined | ||
643 | } | ||
552 | 644 | ||
553 | { | 645 | await deleteMe(server.url, accessToken) |
554 | const res = await getUsersList(server.url, server.accessToken) | 646 | |
555 | expect(res.body.data.find(u => u.username === 'user_15')).to.be.undefined | 647 | { |
556 | } | 648 | const res = await getUsersList(server.url, server.accessToken) |
649 | expect(res.body.data.find(u => u.username === 'user_15')).to.be.undefined | ||
650 | } | ||
651 | }) | ||
557 | }) | 652 | }) |
558 | 653 | ||
559 | it('Should block and unblock a user', async function () { | 654 | describe('User blocking', function () { |
560 | const user16 = { | 655 | it('Should block and unblock a user', async function () { |
561 | username: 'user_16', | 656 | const user16 = { |
562 | password: 'my super password' | 657 | username: 'user_16', |
563 | } | 658 | password: 'my super password' |
564 | const resUser = await createUser(server.url, server.accessToken, user16.username, user16.password) | 659 | } |
565 | const user16Id = resUser.body.user.id | 660 | const resUser = await createUser({ |
661 | url: server.url, | ||
662 | accessToken: server.accessToken, | ||
663 | username: user16.username, | ||
664 | password: user16.password | ||
665 | }) | ||
666 | const user16Id = resUser.body.user.id | ||
566 | 667 | ||
567 | accessToken = await userLogin(server, user16) | 668 | accessToken = await userLogin(server, user16) |
568 | 669 | ||
569 | await getMyUserInformation(server.url, accessToken, 200) | 670 | await getMyUserInformation(server.url, accessToken, 200) |
570 | await blockUser(server.url, user16Id, server.accessToken) | 671 | await blockUser(server.url, user16Id, server.accessToken) |
571 | 672 | ||
572 | await getMyUserInformation(server.url, accessToken, 401) | 673 | await getMyUserInformation(server.url, accessToken, 401) |
573 | await userLogin(server, user16, 400) | 674 | await userLogin(server, user16, 400) |
574 | 675 | ||
575 | await unblockUser(server.url, user16Id, server.accessToken) | 676 | await unblockUser(server.url, user16Id, server.accessToken) |
576 | accessToken = await userLogin(server, user16) | 677 | accessToken = await userLogin(server, user16) |
577 | await getMyUserInformation(server.url, accessToken, 200) | 678 | await getMyUserInformation(server.url, accessToken, 200) |
679 | }) | ||
578 | }) | 680 | }) |
579 | 681 | ||
580 | after(async function () { | 682 | after(async function () { |
581 | killallServers([ server ]) | 683 | await cleanupTests([ server ]) |
582 | |||
583 | // Keep the logs if the test failed | ||
584 | if (this[ 'ok' ]) { | ||
585 | await flushTests() | ||
586 | } | ||
587 | }) | 684 | }) |
588 | }) | 685 | }) |
diff --git a/server/tests/api/videos/index.ts b/server/tests/api/videos/index.ts index 97f467aae..93e1f3e98 100644 --- a/server/tests/api/videos/index.ts +++ b/server/tests/api/videos/index.ts | |||
@@ -8,11 +8,14 @@ import './video-change-ownership' | |||
8 | import './video-channels' | 8 | import './video-channels' |
9 | import './video-comments' | 9 | import './video-comments' |
10 | import './video-description' | 10 | import './video-description' |
11 | import './video-hls' | ||
11 | import './video-imports' | 12 | import './video-imports' |
12 | import './video-nsfw' | 13 | import './video-nsfw' |
14 | import './video-playlists' | ||
13 | import './video-privacy' | 15 | import './video-privacy' |
14 | import './video-schedule-update' | 16 | import './video-schedule-update' |
15 | import './video-transcoder' | 17 | import './video-transcoder' |
16 | import './videos-filter' | 18 | import './videos-filter' |
17 | import './videos-history' | 19 | import './videos-history' |
18 | import './videos-overview' | 20 | import './videos-overview' |
21 | import './videos-views-cleaner' | ||
diff --git a/server/tests/api/videos/multiple-servers.ts b/server/tests/api/videos/multiple-servers.ts index 6c281e49e..68c1e9a8d 100644 --- a/server/tests/api/videos/multiple-servers.ts +++ b/server/tests/api/videos/multiple-servers.ts | |||
@@ -9,7 +9,7 @@ import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/ | |||
9 | import { | 9 | import { |
10 | addVideoChannel, | 10 | addVideoChannel, |
11 | checkTmpIsEmpty, | 11 | checkTmpIsEmpty, |
12 | checkVideoFilesWereRemoved, | 12 | checkVideoFilesWereRemoved, cleanupTests, |
13 | completeVideoCheck, | 13 | completeVideoCheck, |
14 | createUser, | 14 | createUser, |
15 | dateIsValid, | 15 | dateIsValid, |
@@ -32,15 +32,15 @@ import { | |||
32 | viewVideo, | 32 | viewVideo, |
33 | wait, | 33 | wait, |
34 | webtorrentAdd | 34 | webtorrentAdd |
35 | } from '../../../../shared/utils' | 35 | } from '../../../../shared/extra-utils' |
36 | import { | 36 | import { |
37 | addVideoCommentReply, | 37 | addVideoCommentReply, |
38 | addVideoCommentThread, | 38 | addVideoCommentThread, |
39 | deleteVideoComment, | 39 | deleteVideoComment, |
40 | getVideoCommentThreads, | 40 | getVideoCommentThreads, |
41 | getVideoThreadComments | 41 | getVideoThreadComments |
42 | } from '../../../../shared/utils/videos/video-comments' | 42 | } from '../../../../shared/extra-utils/videos/video-comments' |
43 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 43 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
44 | 44 | ||
45 | const expect = chai.expect | 45 | const expect = chai.expect |
46 | 46 | ||
@@ -98,6 +98,7 @@ describe('Test multiple servers', function () { | |||
98 | nsfw: true, | 98 | nsfw: true, |
99 | description: 'my super description for server 1', | 99 | description: 'my super description for server 1', |
100 | support: 'my super support text for server 1', | 100 | support: 'my super support text for server 1', |
101 | originallyPublishedAt: '2019-02-10T13:38:14.449Z', | ||
101 | tags: [ 'tag1p1', 'tag2p1' ], | 102 | tags: [ 'tag1p1', 'tag2p1' ], |
102 | channelId: videoChannelId, | 103 | channelId: videoChannelId, |
103 | fixture: 'video_short1.webm' | 104 | fixture: 'video_short1.webm' |
@@ -118,6 +119,7 @@ describe('Test multiple servers', function () { | |||
118 | nsfw: true, | 119 | nsfw: true, |
119 | description: 'my super description for server 1', | 120 | description: 'my super description for server 1', |
120 | support: 'my super support text for server 1', | 121 | support: 'my super support text for server 1', |
122 | originallyPublishedAt: '2019-02-10T13:38:14.449Z', | ||
121 | account: { | 123 | account: { |
122 | name: 'root', | 124 | name: 'root', |
123 | host: 'localhost:9001' | 125 | host: 'localhost:9001' |
@@ -128,6 +130,7 @@ describe('Test multiple servers', function () { | |||
128 | tags: [ 'tag1p1', 'tag2p1' ], | 130 | tags: [ 'tag1p1', 'tag2p1' ], |
129 | privacy: VideoPrivacy.PUBLIC, | 131 | privacy: VideoPrivacy.PUBLIC, |
130 | commentsEnabled: true, | 132 | commentsEnabled: true, |
133 | downloadEnabled: true, | ||
131 | channel: { | 134 | channel: { |
132 | displayName: 'my channel', | 135 | displayName: 'my channel', |
133 | name: 'super_channel_name', | 136 | name: 'super_channel_name', |
@@ -161,7 +164,7 @@ describe('Test multiple servers', function () { | |||
161 | username: 'user1', | 164 | username: 'user1', |
162 | password: 'super_password' | 165 | password: 'super_password' |
163 | } | 166 | } |
164 | await createUser(servers[1].url, servers[1].accessToken, user.username, user.password) | 167 | await createUser({ url: servers[ 1 ].url, accessToken: servers[ 1 ].accessToken, username: user.username, password: user.password }) |
165 | const userAccessToken = await userLogin(servers[1], user) | 168 | const userAccessToken = await userLogin(servers[1], user) |
166 | 169 | ||
167 | const videoAttributes = { | 170 | const videoAttributes = { |
@@ -199,6 +202,7 @@ describe('Test multiple servers', function () { | |||
199 | }, | 202 | }, |
200 | isLocal, | 203 | isLocal, |
201 | commentsEnabled: true, | 204 | commentsEnabled: true, |
205 | downloadEnabled: true, | ||
202 | duration: 5, | 206 | duration: 5, |
203 | tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], | 207 | tags: [ 'tag1p2', 'tag2p2', 'tag3p2' ], |
204 | privacy: VideoPrivacy.PUBLIC, | 208 | privacy: VideoPrivacy.PUBLIC, |
@@ -307,6 +311,7 @@ describe('Test multiple servers', function () { | |||
307 | isLocal, | 311 | isLocal, |
308 | duration: 5, | 312 | duration: 5, |
309 | commentsEnabled: true, | 313 | commentsEnabled: true, |
314 | downloadEnabled: true, | ||
310 | tags: [ 'tag1p3' ], | 315 | tags: [ 'tag1p3' ], |
311 | privacy: VideoPrivacy.PUBLIC, | 316 | privacy: VideoPrivacy.PUBLIC, |
312 | channel: { | 317 | channel: { |
@@ -338,6 +343,7 @@ describe('Test multiple servers', function () { | |||
338 | host: 'localhost:9003' | 343 | host: 'localhost:9003' |
339 | }, | 344 | }, |
340 | commentsEnabled: true, | 345 | commentsEnabled: true, |
346 | downloadEnabled: true, | ||
341 | isLocal, | 347 | isLocal, |
342 | duration: 5, | 348 | duration: 5, |
343 | tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ], | 349 | tags: [ 'tag2p3', 'tag3p3', 'tag4p3' ], |
@@ -573,15 +579,15 @@ describe('Test multiple servers', function () { | |||
573 | this.timeout(20000) | 579 | this.timeout(20000) |
574 | 580 | ||
575 | await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like') | 581 | await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like') |
576 | await wait(200) | 582 | await wait(500) |
577 | await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'dislike') | 583 | await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'dislike') |
578 | await wait(200) | 584 | await wait(500) |
579 | await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like') | 585 | await rateVideo(servers[0].url, servers[0].accessToken, remoteVideosServer1[0], 'like') |
580 | await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'like') | 586 | await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'like') |
581 | await wait(200) | 587 | await wait(500) |
582 | await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'dislike') | 588 | await rateVideo(servers[2].url, servers[2].accessToken, localVideosServer3[1], 'dislike') |
583 | await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[1], 'dislike') | 589 | await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[1], 'dislike') |
584 | await wait(200) | 590 | await wait(500) |
585 | await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[0], 'like') | 591 | await rateVideo(servers[2].url, servers[2].accessToken, remoteVideosServer3[0], 'like') |
586 | 592 | ||
587 | await waitJobs(servers) | 593 | await waitJobs(servers) |
@@ -621,6 +627,7 @@ describe('Test multiple servers', function () { | |||
621 | support: 'my super support text updated', | 627 | support: 'my super support text updated', |
622 | tags: [ 'tag_up_1', 'tag_up_2' ], | 628 | tags: [ 'tag_up_1', 'tag_up_2' ], |
623 | thumbnailfile: 'thumbnail.jpg', | 629 | thumbnailfile: 'thumbnail.jpg', |
630 | originallyPublishedAt: '2019-02-11T13:38:14.449Z', | ||
624 | previewfile: 'preview.jpg' | 631 | previewfile: 'preview.jpg' |
625 | } | 632 | } |
626 | 633 | ||
@@ -648,6 +655,7 @@ describe('Test multiple servers', function () { | |||
648 | nsfw: true, | 655 | nsfw: true, |
649 | description: 'my super description updated', | 656 | description: 'my super description updated', |
650 | support: 'my super support text updated', | 657 | support: 'my super support text updated', |
658 | originallyPublishedAt: '2019-02-11T13:38:14.449Z', | ||
651 | account: { | 659 | account: { |
652 | name: 'root', | 660 | name: 'root', |
653 | host: 'localhost:9003' | 661 | host: 'localhost:9003' |
@@ -655,6 +663,7 @@ describe('Test multiple servers', function () { | |||
655 | isLocal, | 663 | isLocal, |
656 | duration: 5, | 664 | duration: 5, |
657 | commentsEnabled: true, | 665 | commentsEnabled: true, |
666 | downloadEnabled: true, | ||
658 | tags: [ 'tag_up_1', 'tag_up_2' ], | 667 | tags: [ 'tag_up_1', 'tag_up_2' ], |
659 | privacy: VideoPrivacy.PUBLIC, | 668 | privacy: VideoPrivacy.PUBLIC, |
660 | channel: { | 669 | channel: { |
@@ -914,11 +923,12 @@ describe('Test multiple servers', function () { | |||
914 | } | 923 | } |
915 | }) | 924 | }) |
916 | 925 | ||
917 | it('Should disable comments', async function () { | 926 | it('Should disable comments and download', async function () { |
918 | this.timeout(20000) | 927 | this.timeout(20000) |
919 | 928 | ||
920 | const attributes = { | 929 | const attributes = { |
921 | commentsEnabled: false | 930 | commentsEnabled: false, |
931 | downloadEnabled: false | ||
922 | } | 932 | } |
923 | 933 | ||
924 | await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, attributes) | 934 | await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, attributes) |
@@ -928,6 +938,7 @@ describe('Test multiple servers', function () { | |||
928 | for (const server of servers) { | 938 | for (const server of servers) { |
929 | const res = await getVideo(server.url, videoUUID) | 939 | const res = await getVideo(server.url, videoUUID) |
930 | expect(res.body.commentsEnabled).to.be.false | 940 | expect(res.body.commentsEnabled).to.be.false |
941 | expect(res.body.downloadEnabled).to.be.false | ||
931 | 942 | ||
932 | const text = 'my super forbidden comment' | 943 | const text = 'my super forbidden comment' |
933 | await addVideoCommentThread(server.url, server.accessToken, videoUUID, text, 409) | 944 | await addVideoCommentThread(server.url, server.accessToken, videoUUID, text, 409) |
@@ -976,6 +987,7 @@ describe('Test multiple servers', function () { | |||
976 | isLocal, | 987 | isLocal, |
977 | duration: 5, | 988 | duration: 5, |
978 | commentsEnabled: false, | 989 | commentsEnabled: false, |
990 | downloadEnabled: true, | ||
979 | tags: [ ], | 991 | tags: [ ], |
980 | privacy: VideoPrivacy.PUBLIC, | 992 | privacy: VideoPrivacy.PUBLIC, |
981 | channel: { | 993 | channel: { |
@@ -1018,11 +1030,6 @@ describe('Test multiple servers', function () { | |||
1018 | }) | 1030 | }) |
1019 | 1031 | ||
1020 | after(async function () { | 1032 | after(async function () { |
1021 | killallServers(servers) | 1033 | await cleanupTests(servers) |
1022 | |||
1023 | // Keep the logs if the test failed | ||
1024 | if (this['ok']) { | ||
1025 | await flushTests() | ||
1026 | } | ||
1027 | }) | 1034 | }) |
1028 | }) | 1035 | }) |
diff --git a/server/tests/api/videos/services.ts b/server/tests/api/videos/services.ts index 2da86964f..e9ad947b2 100644 --- a/server/tests/api/videos/services.ts +++ b/server/tests/api/videos/services.ts | |||
@@ -2,16 +2,8 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { getOEmbed, getVideosList, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../../../shared/extra-utils/index' |
6 | flushTests, | 6 | import { cleanupTests, flushAndRunServer } from '../../../../shared/extra-utils/server/servers' |
7 | getOEmbed, | ||
8 | getVideosList, | ||
9 | killallServers, | ||
10 | ServerInfo, | ||
11 | setAccessTokensToServers, | ||
12 | uploadVideo | ||
13 | } from '../../../../shared/utils/index' | ||
14 | import { runServer } from '../../../../shared/utils/server/servers' | ||
15 | 7 | ||
16 | const expect = chai.expect | 8 | const expect = chai.expect |
17 | 9 | ||
@@ -21,9 +13,7 @@ describe('Test services', function () { | |||
21 | before(async function () { | 13 | before(async function () { |
22 | this.timeout(30000) | 14 | this.timeout(30000) |
23 | 15 | ||
24 | await flushTests() | 16 | server = await flushAndRunServer(1) |
25 | |||
26 | server = await runServer(1) | ||
27 | 17 | ||
28 | await setAccessTokensToServers([ server ]) | 18 | await setAccessTokensToServers([ server ]) |
29 | 19 | ||
@@ -77,6 +67,6 @@ describe('Test services', function () { | |||
77 | }) | 67 | }) |
78 | 68 | ||
79 | after(async function () { | 69 | after(async function () { |
80 | killallServers([ server ]) | 70 | await cleanupTests([ server ]) |
81 | }) | 71 | }) |
82 | }) | 72 | }) |
diff --git a/server/tests/api/videos/single-server.ts b/server/tests/api/videos/single-server.ts index 069dec67c..1f366b642 100644 --- a/server/tests/api/videos/single-server.ts +++ b/server/tests/api/videos/single-server.ts | |||
@@ -6,8 +6,9 @@ import 'mocha' | |||
6 | import { VideoPrivacy } from '../../../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../../../shared/models/videos' |
7 | import { | 7 | import { |
8 | checkVideoFilesWereRemoved, | 8 | checkVideoFilesWereRemoved, |
9 | cleanupTests, | ||
9 | completeVideoCheck, | 10 | completeVideoCheck, |
10 | flushTests, | 11 | flushAndRunServer, |
11 | getVideo, | 12 | getVideo, |
12 | getVideoCategories, | 13 | getVideoCategories, |
13 | getVideoLanguages, | 14 | getVideoLanguages, |
@@ -17,10 +18,8 @@ import { | |||
17 | getVideosListPagination, | 18 | getVideosListPagination, |
18 | getVideosListSort, | 19 | getVideosListSort, |
19 | getVideosWithFilters, | 20 | getVideosWithFilters, |
20 | killallServers, | ||
21 | rateVideo, | 21 | rateVideo, |
22 | removeVideo, | 22 | removeVideo, |
23 | runServer, | ||
24 | ServerInfo, | 23 | ServerInfo, |
25 | setAccessTokensToServers, | 24 | setAccessTokensToServers, |
26 | testImage, | 25 | testImage, |
@@ -28,7 +27,7 @@ import { | |||
28 | uploadVideo, | 27 | uploadVideo, |
29 | viewVideo, | 28 | viewVideo, |
30 | wait | 29 | wait |
31 | } from '../../../../shared/utils' | 30 | } from '../../../../shared/extra-utils' |
32 | 31 | ||
33 | const expect = chai.expect | 32 | const expect = chai.expect |
34 | 33 | ||
@@ -55,6 +54,7 @@ describe('Test a single server', function () { | |||
55 | tags: [ 'tag1', 'tag2', 'tag3' ], | 54 | tags: [ 'tag1', 'tag2', 'tag3' ], |
56 | privacy: VideoPrivacy.PUBLIC, | 55 | privacy: VideoPrivacy.PUBLIC, |
57 | commentsEnabled: true, | 56 | commentsEnabled: true, |
57 | downloadEnabled: true, | ||
58 | channel: { | 58 | channel: { |
59 | displayName: 'Main root channel', | 59 | displayName: 'Main root channel', |
60 | name: 'root_channel', | 60 | name: 'root_channel', |
@@ -87,6 +87,7 @@ describe('Test a single server', function () { | |||
87 | privacy: VideoPrivacy.PUBLIC, | 87 | privacy: VideoPrivacy.PUBLIC, |
88 | duration: 5, | 88 | duration: 5, |
89 | commentsEnabled: false, | 89 | commentsEnabled: false, |
90 | downloadEnabled: false, | ||
90 | channel: { | 91 | channel: { |
91 | name: 'root_channel', | 92 | name: 'root_channel', |
92 | displayName: 'Main root channel', | 93 | displayName: 'Main root channel', |
@@ -105,9 +106,7 @@ describe('Test a single server', function () { | |||
105 | before(async function () { | 106 | before(async function () { |
106 | this.timeout(30000) | 107 | this.timeout(30000) |
107 | 108 | ||
108 | await flushTests() | 109 | server = await flushAndRunServer(1) |
109 | |||
110 | server = await runServer(1) | ||
111 | 110 | ||
112 | await setAccessTokensToServers([ server ]) | 111 | await setAccessTokensToServers([ server ]) |
113 | }) | 112 | }) |
@@ -356,6 +355,7 @@ describe('Test a single server', function () { | |||
356 | nsfw: false, | 355 | nsfw: false, |
357 | description: 'my super description updated', | 356 | description: 'my super description updated', |
358 | commentsEnabled: false, | 357 | commentsEnabled: false, |
358 | downloadEnabled: false, | ||
359 | tags: [ 'tagup1', 'tagup2' ] | 359 | tags: [ 'tagup1', 'tagup2' ] |
360 | } | 360 | } |
361 | await updateVideo(server.url, server.accessToken, videoId, attributes) | 361 | await updateVideo(server.url, server.accessToken, videoId, attributes) |
@@ -424,11 +424,6 @@ describe('Test a single server', function () { | |||
424 | }) | 424 | }) |
425 | 425 | ||
426 | after(async function () { | 426 | after(async function () { |
427 | killallServers([ server ]) | 427 | await cleanupTests([ server ]) |
428 | |||
429 | // Keep the logs if the test failed | ||
430 | if (this['ok']) { | ||
431 | await flushTests() | ||
432 | } | ||
433 | }) | 428 | }) |
434 | }) | 429 | }) |
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts index 3a7b623da..7318497d5 100644 --- a/server/tests/api/videos/video-abuse.ts +++ b/server/tests/api/videos/video-abuse.ts | |||
@@ -4,6 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoAbuse, VideoAbuseState } from '../../../../shared/models/videos' | 5 | import { VideoAbuse, VideoAbuseState } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | deleteVideoAbuse, | 8 | deleteVideoAbuse, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
9 | getVideoAbusesList, | 10 | getVideoAbusesList, |
@@ -14,9 +15,9 @@ import { | |||
14 | setAccessTokensToServers, | 15 | setAccessTokensToServers, |
15 | updateVideoAbuse, | 16 | updateVideoAbuse, |
16 | uploadVideo | 17 | uploadVideo |
17 | } from '../../../../shared/utils/index' | 18 | } from '../../../../shared/extra-utils/index' |
18 | import { doubleFollow } from '../../../../shared/utils/server/follows' | 19 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
19 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 20 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
20 | 21 | ||
21 | const expect = chai.expect | 22 | const expect = chai.expect |
22 | 23 | ||
@@ -173,6 +174,6 @@ describe('Test video abuses', function () { | |||
173 | }) | 174 | }) |
174 | 175 | ||
175 | after(async function () { | 176 | after(async function () { |
176 | killallServers(servers) | 177 | await cleanupTests(servers) |
177 | }) | 178 | }) |
178 | }) | 179 | }) |
diff --git a/server/tests/api/videos/video-blacklist.ts b/server/tests/api/videos/video-blacklist.ts index d39ad63b4..e907bbdc0 100644 --- a/server/tests/api/videos/video-blacklist.ts +++ b/server/tests/api/videos/video-blacklist.ts | |||
@@ -4,29 +4,32 @@ import * as chai from 'chai' | |||
4 | import { orderBy } from 'lodash' | 4 | import { orderBy } from 'lodash' |
5 | import 'mocha' | 5 | import 'mocha' |
6 | import { | 6 | import { |
7 | addVideoToBlacklist, | 7 | addVideoToBlacklist, cleanupTests, |
8 | createUser, | ||
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
9 | getBlacklistedVideosList, | 10 | getBlacklistedVideosList, |
10 | getMyVideos, | 11 | getMyVideos, |
11 | getSortedBlacklistedVideosList, | ||
12 | getVideosList, | 12 | getVideosList, |
13 | killallServers, | 13 | killallServers, |
14 | removeVideoFromBlacklist, | 14 | removeVideoFromBlacklist, |
15 | reRunServer, | ||
15 | searchVideo, | 16 | searchVideo, |
16 | ServerInfo, | 17 | ServerInfo, |
17 | setAccessTokensToServers, | 18 | setAccessTokensToServers, |
18 | updateVideo, | 19 | updateVideo, |
19 | updateVideoBlacklist, | 20 | updateVideoBlacklist, |
20 | uploadVideo, | 21 | uploadVideo, |
21 | viewVideo | 22 | userLogin |
22 | } from '../../../../shared/utils/index' | 23 | } from '../../../../shared/extra-utils/index' |
23 | import { doubleFollow } from '../../../../shared/utils/server/follows' | 24 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
24 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 25 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
25 | import { VideoBlacklist } from '../../../../shared/models/videos' | 26 | import { VideoBlacklist, VideoBlacklistType } from '../../../../shared/models/videos' |
27 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | ||
28 | import { UserRole } from '../../../../shared/models/users' | ||
26 | 29 | ||
27 | const expect = chai.expect | 30 | const expect = chai.expect |
28 | 31 | ||
29 | describe('Test video blacklist management', function () { | 32 | describe('Test video blacklist', function () { |
30 | let servers: ServerInfo[] = [] | 33 | let servers: ServerInfo[] = [] |
31 | let videoId: number | 34 | let videoId: number |
32 | 35 | ||
@@ -101,9 +104,9 @@ describe('Test video blacklist management', function () { | |||
101 | }) | 104 | }) |
102 | }) | 105 | }) |
103 | 106 | ||
104 | describe('When listing blacklisted videos', function () { | 107 | describe('When listing manually blacklisted videos', function () { |
105 | it('Should display all the blacklisted videos', async function () { | 108 | it('Should display all the blacklisted videos', async function () { |
106 | const res = await getBlacklistedVideosList(servers[0].url, servers[0].accessToken) | 109 | const res = await getBlacklistedVideosList({ url: servers[0].url, token: servers[0].accessToken }) |
107 | 110 | ||
108 | expect(res.body.total).to.equal(2) | 111 | expect(res.body.total).to.equal(2) |
109 | 112 | ||
@@ -117,8 +120,36 @@ describe('Test video blacklist management', function () { | |||
117 | } | 120 | } |
118 | }) | 121 | }) |
119 | 122 | ||
123 | it('Should display all the blacklisted videos when applying manual type filter', async function () { | ||
124 | const res = await getBlacklistedVideosList({ | ||
125 | url: servers[ 0 ].url, | ||
126 | token: servers[ 0 ].accessToken, | ||
127 | type: VideoBlacklistType.MANUAL | ||
128 | }) | ||
129 | |||
130 | expect(res.body.total).to.equal(2) | ||
131 | |||
132 | const blacklistedVideos = res.body.data | ||
133 | expect(blacklistedVideos).to.be.an('array') | ||
134 | expect(blacklistedVideos.length).to.equal(2) | ||
135 | }) | ||
136 | |||
137 | it('Should display nothing when applying automatic type filter', async function () { | ||
138 | const res = await getBlacklistedVideosList({ | ||
139 | url: servers[ 0 ].url, | ||
140 | token: servers[ 0 ].accessToken, | ||
141 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | ||
142 | }) | ||
143 | |||
144 | expect(res.body.total).to.equal(0) | ||
145 | |||
146 | const blacklistedVideos = res.body.data | ||
147 | expect(blacklistedVideos).to.be.an('array') | ||
148 | expect(blacklistedVideos.length).to.equal(0) | ||
149 | }) | ||
150 | |||
120 | it('Should get the correct sort when sorting by descending id', async function () { | 151 | it('Should get the correct sort when sorting by descending id', async function () { |
121 | const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-id') | 152 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-id' }) |
122 | expect(res.body.total).to.equal(2) | 153 | expect(res.body.total).to.equal(2) |
123 | 154 | ||
124 | const blacklistedVideos = res.body.data | 155 | const blacklistedVideos = res.body.data |
@@ -131,7 +162,7 @@ describe('Test video blacklist management', function () { | |||
131 | }) | 162 | }) |
132 | 163 | ||
133 | it('Should get the correct sort when sorting by descending video name', async function () { | 164 | it('Should get the correct sort when sorting by descending video name', async function () { |
134 | const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name') | 165 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) |
135 | expect(res.body.total).to.equal(2) | 166 | expect(res.body.total).to.equal(2) |
136 | 167 | ||
137 | const blacklistedVideos = res.body.data | 168 | const blacklistedVideos = res.body.data |
@@ -144,7 +175,7 @@ describe('Test video blacklist management', function () { | |||
144 | }) | 175 | }) |
145 | 176 | ||
146 | it('Should get the correct sort when sorting by ascending creation date', async function () { | 177 | it('Should get the correct sort when sorting by ascending creation date', async function () { |
147 | const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt') | 178 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' }) |
148 | expect(res.body.total).to.equal(2) | 179 | expect(res.body.total).to.equal(2) |
149 | 180 | ||
150 | const blacklistedVideos = res.body.data | 181 | const blacklistedVideos = res.body.data |
@@ -161,7 +192,7 @@ describe('Test video blacklist management', function () { | |||
161 | it('Should change the reason', async function () { | 192 | it('Should change the reason', async function () { |
162 | await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated') | 193 | await updateVideoBlacklist(servers[0].url, servers[0].accessToken, videoId, 'my super reason updated') |
163 | 194 | ||
164 | const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name') | 195 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) |
165 | const video = res.body.data.find(b => b.video.id === videoId) | 196 | const video = res.body.data.find(b => b.video.id === videoId) |
166 | 197 | ||
167 | expect(video.reason).to.equal('my super reason updated') | 198 | expect(video.reason).to.equal('my super reason updated') |
@@ -197,7 +228,7 @@ describe('Test video blacklist management', function () { | |||
197 | 228 | ||
198 | it('Should remove a video from the blacklist on server 1', async function () { | 229 | it('Should remove a video from the blacklist on server 1', async function () { |
199 | // Get one video in the blacklist | 230 | // Get one video in the blacklist |
200 | const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name') | 231 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) |
201 | videoToRemove = res.body.data[0] | 232 | videoToRemove = res.body.data[0] |
202 | blacklist = res.body.data.slice(1) | 233 | blacklist = res.body.data.slice(1) |
203 | 234 | ||
@@ -218,7 +249,7 @@ describe('Test video blacklist management', function () { | |||
218 | }) | 249 | }) |
219 | 250 | ||
220 | it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () { | 251 | it('Should not have the ex-blacklisted video in videos blacklist list on server 1', async function () { |
221 | const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, '-name') | 252 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: '-name' }) |
222 | expect(res.body.total).to.equal(1) | 253 | expect(res.body.total).to.equal(1) |
223 | 254 | ||
224 | const videos = res.body.data | 255 | const videos = res.body.data |
@@ -292,7 +323,7 @@ describe('Test video blacklist management', function () { | |||
292 | }) | 323 | }) |
293 | 324 | ||
294 | it('Should have the correct video blacklist unfederate attribute', async function () { | 325 | it('Should have the correct video blacklist unfederate attribute', async function () { |
295 | const res = await getSortedBlacklistedVideosList(servers[0].url, servers[0].accessToken, 'createdAt') | 326 | const res = await getBlacklistedVideosList({ url: servers[ 0 ].url, token: servers[ 0 ].accessToken, sort: 'createdAt' }) |
296 | 327 | ||
297 | const blacklistedVideos: VideoBlacklist[] = res.body.data | 328 | const blacklistedVideos: VideoBlacklist[] = res.body.data |
298 | const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID) | 329 | const video3Blacklisted = blacklistedVideos.find(b => b.video.uuid === video3UUID) |
@@ -317,7 +348,84 @@ describe('Test video blacklist management', function () { | |||
317 | 348 | ||
318 | }) | 349 | }) |
319 | 350 | ||
351 | describe('When auto blacklist videos', function () { | ||
352 | let userWithoutFlag: string | ||
353 | let userWithFlag: string | ||
354 | |||
355 | before(async function () { | ||
356 | this.timeout(20000) | ||
357 | |||
358 | killallServers([ servers[0] ]) | ||
359 | |||
360 | const config = { | ||
361 | 'auto_blacklist': { | ||
362 | videos: { | ||
363 | 'of_users': { | ||
364 | enabled: true | ||
365 | } | ||
366 | } | ||
367 | } | ||
368 | } | ||
369 | await reRunServer(servers[0], config) | ||
370 | |||
371 | { | ||
372 | const user = { username: 'user_without_flag', password: 'password' } | ||
373 | await createUser({ | ||
374 | url: servers[ 0 ].url, | ||
375 | accessToken: servers[ 0 ].accessToken, | ||
376 | username: user.username, | ||
377 | adminFlags: UserAdminFlag.NONE, | ||
378 | password: user.password, | ||
379 | role: UserRole.USER | ||
380 | }) | ||
381 | |||
382 | userWithoutFlag = await userLogin(servers[0], user) | ||
383 | } | ||
384 | |||
385 | { | ||
386 | const user = { username: 'user_with_flag', password: 'password' } | ||
387 | await createUser({ | ||
388 | url: servers[ 0 ].url, | ||
389 | accessToken: servers[ 0 ].accessToken, | ||
390 | username: user.username, | ||
391 | adminFlags: UserAdminFlag.BY_PASS_VIDEO_AUTO_BLACKLIST, | ||
392 | password: user.password, | ||
393 | role: UserRole.USER | ||
394 | }) | ||
395 | |||
396 | userWithFlag = await userLogin(servers[0], user) | ||
397 | } | ||
398 | |||
399 | await waitJobs(servers) | ||
400 | }) | ||
401 | |||
402 | it('Should auto blacklist a video', async function () { | ||
403 | await uploadVideo(servers[0].url, userWithoutFlag, { name: 'blacklisted' }) | ||
404 | |||
405 | const res = await getBlacklistedVideosList({ | ||
406 | url: servers[ 0 ].url, | ||
407 | token: servers[ 0 ].accessToken, | ||
408 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | ||
409 | }) | ||
410 | |||
411 | expect(res.body.total).to.equal(1) | ||
412 | expect(res.body.data[0].video.name).to.equal('blacklisted') | ||
413 | }) | ||
414 | |||
415 | it('Should not auto blacklist a video', async function () { | ||
416 | await uploadVideo(servers[0].url, userWithFlag, { name: 'not blacklisted' }) | ||
417 | |||
418 | const res = await getBlacklistedVideosList({ | ||
419 | url: servers[ 0 ].url, | ||
420 | token: servers[ 0 ].accessToken, | ||
421 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | ||
422 | }) | ||
423 | |||
424 | expect(res.body.total).to.equal(1) | ||
425 | }) | ||
426 | }) | ||
427 | |||
320 | after(async function () { | 428 | after(async function () { |
321 | killallServers(servers) | 429 | await cleanupTests(servers) |
322 | }) | 430 | }) |
323 | }) | 431 | }) |
diff --git a/server/tests/api/videos/video-captions.ts b/server/tests/api/videos/video-captions.ts index 57bee713f..5e13f5949 100644 --- a/server/tests/api/videos/video-captions.ts +++ b/server/tests/api/videos/video-captions.ts | |||
@@ -3,16 +3,21 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | checkVideoFilesWereRemoved, | 6 | checkVideoFilesWereRemoved, cleanupTests, |
7 | doubleFollow, | 7 | doubleFollow, |
8 | flushAndRunMultipleServers, | 8 | flushAndRunMultipleServers, |
9 | removeVideo, | 9 | removeVideo, |
10 | uploadVideo, | 10 | uploadVideo, |
11 | wait | 11 | wait |
12 | } from '../../../../shared/utils' | 12 | } from '../../../../shared/extra-utils' |
13 | import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/utils/index' | 13 | import { flushTests, killallServers, ServerInfo, setAccessTokensToServers } from '../../../../shared/extra-utils/index' |
14 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 14 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
15 | import { createVideoCaption, deleteVideoCaption, listVideoCaptions, testCaptionFile } from '../../../../shared/utils/videos/video-captions' | 15 | import { |
16 | createVideoCaption, | ||
17 | deleteVideoCaption, | ||
18 | listVideoCaptions, | ||
19 | testCaptionFile | ||
20 | } from '../../../../shared/extra-utils/videos/video-captions' | ||
16 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' | 21 | import { VideoCaption } from '../../../../shared/models/videos/caption/video-caption.model' |
17 | 22 | ||
18 | const expect = chai.expect | 23 | const expect = chai.expect |
@@ -24,8 +29,6 @@ describe('Test video captions', function () { | |||
24 | before(async function () { | 29 | before(async function () { |
25 | this.timeout(30000) | 30 | this.timeout(30000) |
26 | 31 | ||
27 | await flushTests() | ||
28 | |||
29 | servers = await flushAndRunMultipleServers(2) | 32 | servers = await flushAndRunMultipleServers(2) |
30 | 33 | ||
31 | await setAccessTokensToServers(servers) | 34 | await setAccessTokensToServers(servers) |
@@ -193,6 +196,6 @@ describe('Test video captions', function () { | |||
193 | }) | 196 | }) |
194 | 197 | ||
195 | after(async function () { | 198 | after(async function () { |
196 | killallServers(servers) | 199 | await cleanupTests(servers) |
197 | }) | 200 | }) |
198 | }) | 201 | }) |
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts index 25675a966..1c0327d40 100644 --- a/server/tests/api/videos/video-change-ownership.ts +++ b/server/tests/api/videos/video-change-ownership.ts | |||
@@ -4,22 +4,23 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | acceptChangeOwnership, | 6 | acceptChangeOwnership, |
7 | changeVideoOwnership, | 7 | changeVideoOwnership, cleanupTests, |
8 | createUser, doubleFollow, flushAndRunMultipleServers, | 8 | createUser, |
9 | flushTests, | 9 | doubleFollow, |
10 | flushAndRunMultipleServers, | ||
11 | flushAndRunServer, | ||
10 | getMyUserInformation, | 12 | getMyUserInformation, |
13 | getVideo, | ||
11 | getVideoChangeOwnershipList, | 14 | getVideoChangeOwnershipList, |
12 | getVideosList, | 15 | getVideosList, |
13 | killallServers, | 16 | killallServers, |
14 | refuseChangeOwnership, | 17 | refuseChangeOwnership, |
15 | runServer, | ||
16 | ServerInfo, | 18 | ServerInfo, |
17 | setAccessTokensToServers, | 19 | setAccessTokensToServers, |
18 | uploadVideo, | 20 | uploadVideo, |
19 | userLogin, | 21 | userLogin |
20 | getVideo | 22 | } from '../../../../shared/extra-utils' |
21 | } from '../../../../shared/utils' | 23 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
22 | import { waitJobs } from '../../../../shared/utils/server/jobs' | ||
23 | import { User } from '../../../../shared/models/users' | 24 | import { User } from '../../../../shared/models/users' |
24 | import { VideoDetails } from '../../../../shared/models/videos' | 25 | import { VideoDetails } from '../../../../shared/models/videos' |
25 | 26 | ||
@@ -46,8 +47,20 @@ describe('Test video change ownership - nominal', function () { | |||
46 | await setAccessTokensToServers(servers) | 47 | await setAccessTokensToServers(servers) |
47 | 48 | ||
48 | const videoQuota = 42000000 | 49 | const videoQuota = 42000000 |
49 | await createUser(servers[0].url, servers[0].accessToken, firstUser.username, firstUser.password, videoQuota) | 50 | await createUser({ |
50 | await createUser(servers[0].url, servers[0].accessToken, secondUser.username, secondUser.password, videoQuota) | 51 | url: servers[ 0 ].url, |
52 | accessToken: servers[ 0 ].accessToken, | ||
53 | username: firstUser.username, | ||
54 | password: firstUser.password, | ||
55 | videoQuota: videoQuota | ||
56 | }) | ||
57 | await createUser({ | ||
58 | url: servers[ 0 ].url, | ||
59 | accessToken: servers[ 0 ].accessToken, | ||
60 | username: secondUser.username, | ||
61 | password: secondUser.password, | ||
62 | videoQuota: videoQuota | ||
63 | }) | ||
51 | 64 | ||
52 | firstUserAccessToken = await userLogin(servers[0], firstUser) | 65 | firstUserAccessToken = await userLogin(servers[0], firstUser) |
53 | secondUserAccessToken = await userLogin(servers[0], secondUser) | 66 | secondUserAccessToken = await userLogin(servers[0], secondUser) |
@@ -190,7 +203,7 @@ describe('Test video change ownership - nominal', function () { | |||
190 | } | 203 | } |
191 | }) | 204 | }) |
192 | 205 | ||
193 | after(async function () { | 206 | after(function () { |
194 | killallServers(servers) | 207 | killallServers(servers) |
195 | }) | 208 | }) |
196 | }) | 209 | }) |
@@ -213,14 +226,25 @@ describe('Test video change ownership - quota too small', function () { | |||
213 | this.timeout(50000) | 226 | this.timeout(50000) |
214 | 227 | ||
215 | // Run one server | 228 | // Run one server |
216 | await flushTests() | 229 | server = await flushAndRunServer(1) |
217 | server = await runServer(1) | ||
218 | await setAccessTokensToServers([server]) | 230 | await setAccessTokensToServers([server]) |
219 | 231 | ||
220 | const videoQuota = 42000000 | 232 | const videoQuota = 42000000 |
221 | const limitedVideoQuota = 10 | 233 | const limitedVideoQuota = 10 |
222 | await createUser(server.url, server.accessToken, firstUser.username, firstUser.password, videoQuota) | 234 | await createUser({ |
223 | await createUser(server.url, server.accessToken, secondUser.username, secondUser.password, limitedVideoQuota) | 235 | url: server.url, |
236 | accessToken: server.accessToken, | ||
237 | username: firstUser.username, | ||
238 | password: firstUser.password, | ||
239 | videoQuota: videoQuota | ||
240 | }) | ||
241 | await createUser({ | ||
242 | url: server.url, | ||
243 | accessToken: server.accessToken, | ||
244 | username: secondUser.username, | ||
245 | password: secondUser.password, | ||
246 | videoQuota: limitedVideoQuota | ||
247 | }) | ||
224 | 248 | ||
225 | firstUserAccessToken = await userLogin(server, firstUser) | 249 | firstUserAccessToken = await userLogin(server, firstUser) |
226 | secondUserAccessToken = await userLogin(server, secondUser) | 250 | secondUserAccessToken = await userLogin(server, secondUser) |
@@ -274,6 +298,6 @@ describe('Test video change ownership - quota too small', function () { | |||
274 | }) | 298 | }) |
275 | 299 | ||
276 | after(async function () { | 300 | after(async function () { |
277 | killallServers([server]) | 301 | await cleanupTests([ server ]) |
278 | }) | 302 | }) |
279 | }) | 303 | }) |
diff --git a/server/tests/api/videos/video-channels.ts b/server/tests/api/videos/video-channels.ts index 63514d69c..345e96f43 100644 --- a/server/tests/api/videos/video-channels.ts +++ b/server/tests/api/videos/video-channels.ts | |||
@@ -4,6 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { User, Video } from '../../../../shared/index' | 5 | import { User, Video } from '../../../../shared/index' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | createUser, | 8 | createUser, |
8 | doubleFollow, | 9 | doubleFollow, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
@@ -13,7 +14,7 @@ import { | |||
13 | updateVideoChannelAvatar, | 14 | updateVideoChannelAvatar, |
14 | uploadVideo, | 15 | uploadVideo, |
15 | userLogin | 16 | userLogin |
16 | } from '../../../../shared/utils' | 17 | } from '../../../../shared/extra-utils' |
17 | import { | 18 | import { |
18 | addVideoChannel, | 19 | addVideoChannel, |
19 | deleteVideoChannel, | 20 | deleteVideoChannel, |
@@ -26,8 +27,8 @@ import { | |||
26 | ServerInfo, | 27 | ServerInfo, |
27 | setAccessTokensToServers, | 28 | setAccessTokensToServers, |
28 | updateVideoChannel | 29 | updateVideoChannel |
29 | } from '../../../../shared/utils/index' | 30 | } from '../../../../shared/extra-utils/index' |
30 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 31 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
31 | 32 | ||
32 | const expect = chai.expect | 33 | const expect = chai.expect |
33 | 34 | ||
@@ -42,8 +43,6 @@ describe('Test video channels', function () { | |||
42 | before(async function () { | 43 | before(async function () { |
43 | this.timeout(30000) | 44 | this.timeout(30000) |
44 | 45 | ||
45 | await flushTests() | ||
46 | |||
47 | servers = await flushAndRunMultipleServers(2) | 46 | servers = await flushAndRunMultipleServers(2) |
48 | 47 | ||
49 | await setAccessTokensToServers(servers) | 48 | await setAccessTokensToServers(servers) |
@@ -270,7 +269,7 @@ describe('Test video channels', function () { | |||
270 | } | 269 | } |
271 | 270 | ||
272 | { | 271 | { |
273 | await createUser(servers[ 0 ].url, servers[ 0 ].accessToken, 'toto', 'password') | 272 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: 'toto', password: 'password' }) |
274 | const accessToken = await userLogin(servers[ 0 ], { username: 'toto', password: 'password' }) | 273 | const accessToken = await userLogin(servers[ 0 ], { username: 'toto', password: 'password' }) |
275 | 274 | ||
276 | const res = await getMyUserInformation(servers[ 0 ].url, accessToken) | 275 | const res = await getMyUserInformation(servers[ 0 ].url, accessToken) |
@@ -280,6 +279,6 @@ describe('Test video channels', function () { | |||
280 | }) | 279 | }) |
281 | 280 | ||
282 | after(async function () { | 281 | after(async function () { |
283 | killallServers(servers) | 282 | await cleanupTests(servers) |
284 | }) | 283 | }) |
285 | }) | 284 | }) |
diff --git a/server/tests/api/videos/video-comments.ts b/server/tests/api/videos/video-comments.ts index ce1b17e35..22fd8c058 100644 --- a/server/tests/api/videos/video-comments.ts +++ b/server/tests/api/videos/video-comments.ts | |||
@@ -3,24 +3,22 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' | 5 | import { VideoComment, VideoCommentThreadTree } from '../../../../shared/models/videos/video-comment.model' |
6 | import { testImage } from '../../../../shared/utils' | 6 | import { cleanupTests, testImage } from '../../../../shared/extra-utils' |
7 | import { | 7 | import { |
8 | dateIsValid, | 8 | dateIsValid, |
9 | flushTests, | 9 | flushAndRunServer, |
10 | killallServers, | ||
11 | runServer, | ||
12 | ServerInfo, | 10 | ServerInfo, |
13 | setAccessTokensToServers, | 11 | setAccessTokensToServers, |
14 | updateMyAvatar, | 12 | updateMyAvatar, |
15 | uploadVideo | 13 | uploadVideo |
16 | } from '../../../../shared/utils/index' | 14 | } from '../../../../shared/extra-utils/index' |
17 | import { | 15 | import { |
18 | addVideoCommentReply, | 16 | addVideoCommentReply, |
19 | addVideoCommentThread, | 17 | addVideoCommentThread, |
20 | deleteVideoComment, | 18 | deleteVideoComment, |
21 | getVideoCommentThreads, | 19 | getVideoCommentThreads, |
22 | getVideoThreadComments | 20 | getVideoThreadComments |
23 | } from '../../../../shared/utils/videos/video-comments' | 21 | } from '../../../../shared/extra-utils/videos/video-comments' |
24 | 22 | ||
25 | const expect = chai.expect | 23 | const expect = chai.expect |
26 | 24 | ||
@@ -34,9 +32,7 @@ describe('Test video comments', function () { | |||
34 | before(async function () { | 32 | before(async function () { |
35 | this.timeout(30000) | 33 | this.timeout(30000) |
36 | 34 | ||
37 | await flushTests() | 35 | server = await flushAndRunServer(1) |
38 | |||
39 | server = await runServer(1) | ||
40 | 36 | ||
41 | await setAccessTokensToServers([ server ]) | 37 | await setAccessTokensToServers([ server ]) |
42 | 38 | ||
@@ -202,6 +198,6 @@ describe('Test video comments', function () { | |||
202 | }) | 198 | }) |
203 | 199 | ||
204 | after(async function () { | 200 | after(async function () { |
205 | killallServers([ server ]) | 201 | await cleanupTests([ server ]) |
206 | }) | 202 | }) |
207 | }) | 203 | }) |
diff --git a/server/tests/api/videos/video-description.ts b/server/tests/api/videos/video-description.ts index cbda0b9a6..db4d278bf 100644 --- a/server/tests/api/videos/video-description.ts +++ b/server/tests/api/videos/video-description.ts | |||
@@ -3,6 +3,7 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | flushAndRunMultipleServers, | 7 | flushAndRunMultipleServers, |
7 | getVideo, | 8 | getVideo, |
8 | getVideoDescription, | 9 | getVideoDescription, |
@@ -12,9 +13,9 @@ import { | |||
12 | setAccessTokensToServers, | 13 | setAccessTokensToServers, |
13 | updateVideo, | 14 | updateVideo, |
14 | uploadVideo | 15 | uploadVideo |
15 | } from '../../../../shared/utils/index' | 16 | } from '../../../../shared/extra-utils/index' |
16 | import { doubleFollow } from '../../../../shared/utils/server/follows' | 17 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
17 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 18 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
18 | 19 | ||
19 | const expect = chai.expect | 20 | const expect = chai.expect |
20 | 21 | ||
@@ -100,6 +101,6 @@ describe('Test video description', function () { | |||
100 | }) | 101 | }) |
101 | 102 | ||
102 | after(async function () { | 103 | after(async function () { |
103 | killallServers(servers) | 104 | await cleanupTests(servers) |
104 | }) | 105 | }) |
105 | }) | 106 | }) |
diff --git a/server/tests/api/videos/video-hls.ts b/server/tests/api/videos/video-hls.ts new file mode 100644 index 000000000..22031c18b --- /dev/null +++ b/server/tests/api/videos/video-hls.ts | |||
@@ -0,0 +1,134 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | checkDirectoryIsEmpty, | ||
7 | checkSegmentHash, | ||
8 | checkTmpIsEmpty, cleanupTests, | ||
9 | doubleFollow, | ||
10 | flushAndRunMultipleServers, | ||
11 | flushTests, | ||
12 | getPlaylist, | ||
13 | getVideo, | ||
14 | killallServers, | ||
15 | removeVideo, | ||
16 | ServerInfo, | ||
17 | setAccessTokensToServers, | ||
18 | updateVideo, | ||
19 | uploadVideo, | ||
20 | waitJobs | ||
21 | } from '../../../../shared/extra-utils' | ||
22 | import { VideoDetails } from '../../../../shared/models/videos' | ||
23 | import { VideoStreamingPlaylistType } from '../../../../shared/models/videos/video-streaming-playlist.type' | ||
24 | import { join } from 'path' | ||
25 | |||
26 | const expect = chai.expect | ||
27 | |||
28 | async function checkHlsPlaylist (servers: ServerInfo[], videoUUID: string) { | ||
29 | const resolutions = [ 240, 360, 480, 720 ] | ||
30 | |||
31 | for (const server of servers) { | ||
32 | const res = await getVideo(server.url, videoUUID) | ||
33 | const videoDetails: VideoDetails = res.body | ||
34 | |||
35 | expect(videoDetails.streamingPlaylists).to.have.lengthOf(1) | ||
36 | |||
37 | const hlsPlaylist = videoDetails.streamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) | ||
38 | expect(hlsPlaylist).to.not.be.undefined | ||
39 | |||
40 | { | ||
41 | const res2 = await getPlaylist(hlsPlaylist.playlistUrl) | ||
42 | |||
43 | const masterPlaylist = res2.text | ||
44 | |||
45 | expect(masterPlaylist).to.contain('#EXT-X-STREAM-INF:BANDWIDTH=55472,RESOLUTION=640x360,FRAME-RATE=25') | ||
46 | |||
47 | for (const resolution of resolutions) { | ||
48 | expect(masterPlaylist).to.contain(`${resolution}.m3u8`) | ||
49 | } | ||
50 | } | ||
51 | |||
52 | { | ||
53 | for (const resolution of resolutions) { | ||
54 | const res2 = await getPlaylist(`http://localhost:9001/static/streaming-playlists/hls/${videoUUID}/${resolution}.m3u8`) | ||
55 | |||
56 | const subPlaylist = res2.text | ||
57 | expect(subPlaylist).to.contain(`${videoUUID}-${resolution}-fragmented.mp4`) | ||
58 | } | ||
59 | } | ||
60 | |||
61 | { | ||
62 | const baseUrl = 'http://localhost:9001/static/streaming-playlists/hls' | ||
63 | |||
64 | for (const resolution of resolutions) { | ||
65 | await checkSegmentHash(baseUrl, baseUrl, videoUUID, resolution, hlsPlaylist) | ||
66 | } | ||
67 | } | ||
68 | } | ||
69 | } | ||
70 | |||
71 | describe('Test HLS videos', function () { | ||
72 | let servers: ServerInfo[] = [] | ||
73 | let videoUUID = '' | ||
74 | |||
75 | before(async function () { | ||
76 | this.timeout(120000) | ||
77 | |||
78 | servers = await flushAndRunMultipleServers(2, { transcoding: { enabled: true, hls: { enabled: true } } }) | ||
79 | |||
80 | // Get the access tokens | ||
81 | await setAccessTokensToServers(servers) | ||
82 | |||
83 | // Server 1 and server 2 follow each other | ||
84 | await doubleFollow(servers[0], servers[1]) | ||
85 | }) | ||
86 | |||
87 | it('Should upload a video and transcode it to HLS', async function () { | ||
88 | this.timeout(120000) | ||
89 | |||
90 | { | ||
91 | const res = await uploadVideo(servers[ 0 ].url, servers[ 0 ].accessToken, { name: 'video 1', fixture: 'video_short.webm' }) | ||
92 | videoUUID = res.body.video.uuid | ||
93 | } | ||
94 | |||
95 | await waitJobs(servers) | ||
96 | |||
97 | await checkHlsPlaylist(servers, videoUUID) | ||
98 | }) | ||
99 | |||
100 | it('Should update the video', async function () { | ||
101 | await updateVideo(servers[0].url, servers[0].accessToken, videoUUID, { name: 'video 1 updated' }) | ||
102 | |||
103 | await waitJobs(servers) | ||
104 | |||
105 | await checkHlsPlaylist(servers, videoUUID) | ||
106 | }) | ||
107 | |||
108 | it('Should delete the video', async function () { | ||
109 | await removeVideo(servers[0].url, servers[0].accessToken, videoUUID) | ||
110 | |||
111 | await waitJobs(servers) | ||
112 | |||
113 | for (const server of servers) { | ||
114 | await getVideo(server.url, videoUUID, 404) | ||
115 | } | ||
116 | }) | ||
117 | |||
118 | it('Should have the playlists/segment deleted from the disk', async function () { | ||
119 | for (const server of servers) { | ||
120 | await checkDirectoryIsEmpty(server, 'videos') | ||
121 | await checkDirectoryIsEmpty(server, join('streaming-playlists', 'hls')) | ||
122 | } | ||
123 | }) | ||
124 | |||
125 | it('Should have an empty tmp directory', async function () { | ||
126 | for (const server of servers) { | ||
127 | await checkTmpIsEmpty(server) | ||
128 | } | ||
129 | }) | ||
130 | |||
131 | after(async function () { | ||
132 | await cleanupTests(servers) | ||
133 | }) | ||
134 | }) | ||
diff --git a/server/tests/api/videos/video-imports.ts b/server/tests/api/videos/video-imports.ts index cd4988553..1233ed6eb 100644 --- a/server/tests/api/videos/video-imports.ts +++ b/server/tests/api/videos/video-imports.ts | |||
@@ -4,6 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoDetails, VideoImport, VideoPrivacy } from '../../../../shared/models/videos' | 5 | import { VideoDetails, VideoImport, VideoPrivacy } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
9 | getMyUserInformation, | 10 | getMyUserInformation, |
@@ -14,9 +15,9 @@ import { | |||
14 | killallServers, | 15 | killallServers, |
15 | ServerInfo, | 16 | ServerInfo, |
16 | setAccessTokensToServers | 17 | setAccessTokensToServers |
17 | } from '../../../../shared/utils' | 18 | } from '../../../../shared/extra-utils' |
18 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
19 | import { getMagnetURI, getYoutubeVideoUrl, importVideo, getMyVideoImports } from '../../../../shared/utils/videos/video-imports' | 20 | import { getMagnetURI, getYoutubeVideoUrl, importVideo, getMyVideoImports } from '../../../../shared/extra-utils/videos/video-imports' |
20 | 21 | ||
21 | const expect = chai.expect | 22 | const expect = chai.expect |
22 | 23 | ||
@@ -38,6 +39,11 @@ describe('Test video imports', function () { | |||
38 | expect(videoHttp.tags).to.deep.equal([ 'tag1', 'tag2' ]) | 39 | expect(videoHttp.tags).to.deep.equal([ 'tag1', 'tag2' ]) |
39 | expect(videoHttp.files).to.have.lengthOf(1) | 40 | expect(videoHttp.files).to.have.lengthOf(1) |
40 | 41 | ||
42 | const originallyPublishedAt = new Date(videoHttp.originallyPublishedAt) | ||
43 | expect(originallyPublishedAt.getDate()).to.equal(14) | ||
44 | expect(originallyPublishedAt.getMonth()).to.equal(0) | ||
45 | expect(originallyPublishedAt.getFullYear()).to.equal(2019) | ||
46 | |||
41 | const resMagnet = await getVideo(url, idMagnet) | 47 | const resMagnet = await getVideo(url, idMagnet) |
42 | const videoMagnet: VideoDetails = resMagnet.body | 48 | const videoMagnet: VideoDetails = resMagnet.body |
43 | const resTorrent = await getVideo(url, idTorrent) | 49 | const resTorrent = await getVideo(url, idTorrent) |
@@ -237,6 +243,6 @@ describe('Test video imports', function () { | |||
237 | }) | 243 | }) |
238 | 244 | ||
239 | after(async function () { | 245 | after(async function () { |
240 | killallServers(servers) | 246 | await cleanupTests(servers) |
241 | }) | 247 | }) |
242 | }) | 248 | }) |
diff --git a/server/tests/api/videos/video-nsfw.ts b/server/tests/api/videos/video-nsfw.ts index df1ee2eb9..ad6a4b43f 100644 --- a/server/tests/api/videos/video-nsfw.ts +++ b/server/tests/api/videos/video-nsfw.ts | |||
@@ -2,30 +2,23 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { cleanupTests, getVideosList, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../../../shared/extra-utils/index' | ||
6 | import { userLogin } from '../../../../shared/extra-utils/users/login' | ||
7 | import { createUser } from '../../../../shared/extra-utils/users/users' | ||
8 | import { getMyVideos } from '../../../../shared/extra-utils/videos/videos' | ||
5 | import { | 9 | import { |
6 | flushTests, | 10 | flushAndRunServer, |
7 | getVideosList, | ||
8 | killallServers, | ||
9 | ServerInfo, | ||
10 | setAccessTokensToServers, | ||
11 | uploadVideo | ||
12 | } from '../../../../shared/utils/index' | ||
13 | import { userLogin } from '../../../../shared/utils/users/login' | ||
14 | import { createUser } from '../../../../shared/utils/users/users' | ||
15 | import { getMyVideos } from '../../../../shared/utils/videos/videos' | ||
16 | import { | ||
17 | getAccountVideos, | 11 | getAccountVideos, |
18 | getConfig, | 12 | getConfig, |
19 | getCustomConfig, | 13 | getCustomConfig, |
20 | getMyUserInformation, | 14 | getMyUserInformation, |
21 | getVideoChannelVideos, | 15 | getVideoChannelVideos, |
22 | getVideosListWithToken, | 16 | getVideosListWithToken, |
23 | runServer, | ||
24 | searchVideo, | 17 | searchVideo, |
25 | searchVideoWithToken, | 18 | searchVideoWithToken, |
26 | updateCustomConfig, | 19 | updateCustomConfig, |
27 | updateMyUser | 20 | updateMyUser |
28 | } from '../../../../shared/utils' | 21 | } from '../../../../shared/extra-utils' |
29 | import { ServerConfig } from '../../../../shared/models' | 22 | import { ServerConfig } from '../../../../shared/models' |
30 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' | 23 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' |
31 | import { User } from '../../../../shared/models/users' | 24 | import { User } from '../../../../shared/models/users' |
@@ -64,9 +57,7 @@ describe('Test video NSFW policy', function () { | |||
64 | 57 | ||
65 | before(async function () { | 58 | before(async function () { |
66 | this.timeout(50000) | 59 | this.timeout(50000) |
67 | 60 | server = await flushAndRunServer(1) | |
68 | await flushTests() | ||
69 | server = await runServer(1) | ||
70 | 61 | ||
71 | // Get the access tokens | 62 | // Get the access tokens |
72 | await setAccessTokensToServers([ server ]) | 63 | await setAccessTokensToServers([ server ]) |
@@ -144,7 +135,7 @@ describe('Test video NSFW policy', function () { | |||
144 | it('Should create a user having the default nsfw policy', async function () { | 135 | it('Should create a user having the default nsfw policy', async function () { |
145 | const username = 'user1' | 136 | const username = 'user1' |
146 | const password = 'my super password' | 137 | const password = 'my super password' |
147 | await createUser(server.url, server.accessToken, username, password) | 138 | await createUser({ url: server.url, accessToken: server.accessToken, username: username, password: password }) |
148 | 139 | ||
149 | userAccessToken = await userLogin(server, { username, password }) | 140 | userAccessToken = await userLogin(server, { username, password }) |
150 | 141 | ||
@@ -244,6 +235,6 @@ describe('Test video NSFW policy', function () { | |||
244 | }) | 235 | }) |
245 | 236 | ||
246 | after(async function () { | 237 | after(async function () { |
247 | killallServers([ server ]) | 238 | await cleanupTests([ server ]) |
248 | }) | 239 | }) |
249 | }) | 240 | }) |
diff --git a/server/tests/api/videos/video-playlists.ts b/server/tests/api/videos/video-playlists.ts new file mode 100644 index 000000000..e4d817ff8 --- /dev/null +++ b/server/tests/api/videos/video-playlists.ts | |||
@@ -0,0 +1,867 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | addVideoChannel, | ||
7 | addVideoInPlaylist, | ||
8 | checkPlaylistFilesWereRemoved, | ||
9 | cleanupTests, | ||
10 | createUser, | ||
11 | createVideoPlaylist, | ||
12 | deleteVideoChannel, | ||
13 | deleteVideoPlaylist, | ||
14 | doubleFollow, | ||
15 | doVideosExistInMyPlaylist, | ||
16 | flushAndRunMultipleServers, | ||
17 | getAccountPlaylistsList, | ||
18 | getAccountPlaylistsListWithToken, | ||
19 | getMyUserInformation, | ||
20 | getPlaylistVideos, | ||
21 | getVideoChannelPlaylistsList, | ||
22 | getVideoPlaylist, | ||
23 | getVideoPlaylistPrivacies, | ||
24 | getVideoPlaylistsList, | ||
25 | getVideoPlaylistWithToken, | ||
26 | removeUser, | ||
27 | removeVideoFromPlaylist, | ||
28 | reorderVideosPlaylist, | ||
29 | ServerInfo, | ||
30 | setAccessTokensToServers, | ||
31 | setDefaultVideoChannel, | ||
32 | testImage, | ||
33 | unfollow, | ||
34 | updateVideoPlaylist, | ||
35 | updateVideoPlaylistElement, | ||
36 | uploadVideo, | ||
37 | uploadVideoAndGetId, | ||
38 | userLogin, | ||
39 | waitJobs | ||
40 | } from '../../../../shared/extra-utils' | ||
41 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | ||
42 | import { VideoPlaylist } from '../../../../shared/models/videos/playlist/video-playlist.model' | ||
43 | import { Video } from '../../../../shared/models/videos' | ||
44 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' | ||
45 | import { VideoExistInPlaylist } from '../../../../shared/models/videos/playlist/video-exist-in-playlist.model' | ||
46 | import { User } from '../../../../shared/models/users' | ||
47 | |||
48 | const expect = chai.expect | ||
49 | |||
50 | describe('Test video playlists', function () { | ||
51 | let servers: ServerInfo[] = [] | ||
52 | |||
53 | let playlistServer2Id1: number | ||
54 | let playlistServer2Id2: number | ||
55 | let playlistServer2UUID2: number | ||
56 | |||
57 | let playlistServer1Id: number | ||
58 | let playlistServer1UUID: string | ||
59 | |||
60 | let nsfwVideoServer1: number | ||
61 | |||
62 | before(async function () { | ||
63 | this.timeout(120000) | ||
64 | |||
65 | servers = await flushAndRunMultipleServers(3, { transcoding: { enabled: false } }) | ||
66 | |||
67 | // Get the access tokens | ||
68 | await setAccessTokensToServers(servers) | ||
69 | await setDefaultVideoChannel(servers) | ||
70 | |||
71 | // Server 1 and server 2 follow each other | ||
72 | await doubleFollow(servers[0], servers[1]) | ||
73 | // Server 1 and server 3 follow each other | ||
74 | await doubleFollow(servers[0], servers[2]) | ||
75 | |||
76 | { | ||
77 | const serverPromises: Promise<any>[][] = [] | ||
78 | |||
79 | for (const server of servers) { | ||
80 | const videoPromises: Promise<any>[] = [] | ||
81 | |||
82 | for (let i = 0; i < 7; i++) { | ||
83 | videoPromises.push( | ||
84 | uploadVideo(server.url, server.accessToken, { name: `video ${i} server ${server.serverNumber}`, nsfw: false }) | ||
85 | .then(res => res.body.video) | ||
86 | ) | ||
87 | } | ||
88 | |||
89 | serverPromises.push(videoPromises) | ||
90 | } | ||
91 | |||
92 | servers[0].videos = await Promise.all(serverPromises[0]) | ||
93 | servers[1].videos = await Promise.all(serverPromises[1]) | ||
94 | servers[2].videos = await Promise.all(serverPromises[2]) | ||
95 | } | ||
96 | |||
97 | nsfwVideoServer1 = (await uploadVideoAndGetId({ server: servers[ 0 ], videoName: 'NSFW video', nsfw: true })).id | ||
98 | |||
99 | await waitJobs(servers) | ||
100 | }) | ||
101 | |||
102 | it('Should list video playlist privacies', async function () { | ||
103 | const res = await getVideoPlaylistPrivacies(servers[0].url) | ||
104 | |||
105 | const privacies = res.body | ||
106 | expect(Object.keys(privacies)).to.have.length.at.least(3) | ||
107 | |||
108 | expect(privacies[3]).to.equal('Private') | ||
109 | }) | ||
110 | |||
111 | it('Should list watch later playlist', async function () { | ||
112 | const url = servers[ 0 ].url | ||
113 | const accessToken = servers[ 0 ].accessToken | ||
114 | |||
115 | { | ||
116 | const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.WATCH_LATER) | ||
117 | |||
118 | expect(res.body.total).to.equal(1) | ||
119 | expect(res.body.data).to.have.lengthOf(1) | ||
120 | |||
121 | const playlist: VideoPlaylist = res.body.data[ 0 ] | ||
122 | expect(playlist.displayName).to.equal('Watch later') | ||
123 | expect(playlist.type.id).to.equal(VideoPlaylistType.WATCH_LATER) | ||
124 | expect(playlist.type.label).to.equal('Watch later') | ||
125 | } | ||
126 | |||
127 | { | ||
128 | const res = await getAccountPlaylistsListWithToken(url, accessToken, 'root', 0, 5, VideoPlaylistType.REGULAR) | ||
129 | |||
130 | expect(res.body.total).to.equal(0) | ||
131 | expect(res.body.data).to.have.lengthOf(0) | ||
132 | } | ||
133 | |||
134 | { | ||
135 | const res = await getAccountPlaylistsList(url, 'root', 0, 5) | ||
136 | expect(res.body.total).to.equal(0) | ||
137 | expect(res.body.data).to.have.lengthOf(0) | ||
138 | } | ||
139 | }) | ||
140 | |||
141 | it('Should create a playlist on server 1 and have the playlist on server 2 and 3', async function () { | ||
142 | this.timeout(30000) | ||
143 | |||
144 | await createVideoPlaylist({ | ||
145 | url: servers[0].url, | ||
146 | token: servers[0].accessToken, | ||
147 | playlistAttrs: { | ||
148 | displayName: 'my super playlist', | ||
149 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
150 | description: 'my super description', | ||
151 | thumbnailfile: 'thumbnail.jpg', | ||
152 | videoChannelId: servers[0].videoChannel.id | ||
153 | } | ||
154 | }) | ||
155 | |||
156 | await waitJobs(servers) | ||
157 | |||
158 | for (const server of servers) { | ||
159 | const res = await getVideoPlaylistsList(server.url, 0, 5) | ||
160 | expect(res.body.total).to.equal(1) | ||
161 | expect(res.body.data).to.have.lengthOf(1) | ||
162 | |||
163 | const playlistFromList = res.body.data[0] as VideoPlaylist | ||
164 | |||
165 | const res2 = await getVideoPlaylist(server.url, playlistFromList.uuid) | ||
166 | const playlistFromGet = res2.body | ||
167 | |||
168 | for (const playlist of [ playlistFromGet, playlistFromList ]) { | ||
169 | expect(playlist.id).to.be.a('number') | ||
170 | expect(playlist.uuid).to.be.a('string') | ||
171 | |||
172 | expect(playlist.isLocal).to.equal(server.serverNumber === 1) | ||
173 | |||
174 | expect(playlist.displayName).to.equal('my super playlist') | ||
175 | expect(playlist.description).to.equal('my super description') | ||
176 | expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.PUBLIC) | ||
177 | expect(playlist.privacy.label).to.equal('Public') | ||
178 | expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR) | ||
179 | expect(playlist.type.label).to.equal('Regular') | ||
180 | |||
181 | expect(playlist.videosLength).to.equal(0) | ||
182 | |||
183 | expect(playlist.ownerAccount.name).to.equal('root') | ||
184 | expect(playlist.ownerAccount.displayName).to.equal('root') | ||
185 | expect(playlist.videoChannel.name).to.equal('root_channel') | ||
186 | expect(playlist.videoChannel.displayName).to.equal('Main root channel') | ||
187 | } | ||
188 | } | ||
189 | }) | ||
190 | |||
191 | it('Should create a playlist on server 2 and have the playlist on server 1 but not on server 3', async function () { | ||
192 | this.timeout(30000) | ||
193 | |||
194 | { | ||
195 | const res = await createVideoPlaylist({ | ||
196 | url: servers[1].url, | ||
197 | token: servers[1].accessToken, | ||
198 | playlistAttrs: { | ||
199 | displayName: 'playlist 2', | ||
200 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
201 | videoChannelId: servers[1].videoChannel.id | ||
202 | } | ||
203 | }) | ||
204 | playlistServer2Id1 = res.body.videoPlaylist.id | ||
205 | } | ||
206 | |||
207 | { | ||
208 | const res = await createVideoPlaylist({ | ||
209 | url: servers[ 1 ].url, | ||
210 | token: servers[ 1 ].accessToken, | ||
211 | playlistAttrs: { | ||
212 | displayName: 'playlist 3', | ||
213 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
214 | thumbnailfile: 'thumbnail.jpg', | ||
215 | videoChannelId: servers[1].videoChannel.id | ||
216 | } | ||
217 | }) | ||
218 | |||
219 | playlistServer2Id2 = res.body.videoPlaylist.id | ||
220 | playlistServer2UUID2 = res.body.videoPlaylist.uuid | ||
221 | } | ||
222 | |||
223 | for (let id of [ playlistServer2Id1, playlistServer2Id2 ]) { | ||
224 | await addVideoInPlaylist({ | ||
225 | url: servers[ 1 ].url, | ||
226 | token: servers[ 1 ].accessToken, | ||
227 | playlistId: id, | ||
228 | elementAttrs: { videoId: servers[ 1 ].videos[ 0 ].id, startTimestamp: 1, stopTimestamp: 2 } | ||
229 | }) | ||
230 | await addVideoInPlaylist({ | ||
231 | url: servers[ 1 ].url, | ||
232 | token: servers[ 1 ].accessToken, | ||
233 | playlistId: id, | ||
234 | elementAttrs: { videoId: servers[ 1 ].videos[ 1 ].id } | ||
235 | }) | ||
236 | } | ||
237 | |||
238 | await waitJobs(servers) | ||
239 | |||
240 | for (const server of [ servers[0], servers[1] ]) { | ||
241 | const res = await getVideoPlaylistsList(server.url, 0, 5) | ||
242 | |||
243 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') | ||
244 | expect(playlist2).to.not.be.undefined | ||
245 | await testImage(server.url, 'thumbnail-playlist', playlist2.thumbnailPath) | ||
246 | |||
247 | const playlist3 = res.body.data.find(p => p.displayName === 'playlist 3') | ||
248 | expect(playlist3).to.not.be.undefined | ||
249 | await testImage(server.url, 'thumbnail', playlist3.thumbnailPath) | ||
250 | } | ||
251 | |||
252 | const res = await getVideoPlaylistsList(servers[2].url, 0, 5) | ||
253 | expect(res.body.data.find(p => p.displayName === 'playlist 2')).to.be.undefined | ||
254 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.be.undefined | ||
255 | }) | ||
256 | |||
257 | it('Should have the playlist on server 3 after a new follow', async function () { | ||
258 | this.timeout(30000) | ||
259 | |||
260 | // Server 2 and server 3 follow each other | ||
261 | await doubleFollow(servers[1], servers[2]) | ||
262 | |||
263 | const res = await getVideoPlaylistsList(servers[2].url, 0, 5) | ||
264 | |||
265 | const playlist2 = res.body.data.find(p => p.displayName === 'playlist 2') | ||
266 | expect(playlist2).to.not.be.undefined | ||
267 | await testImage(servers[2].url, 'thumbnail-playlist', playlist2.thumbnailPath) | ||
268 | |||
269 | expect(res.body.data.find(p => p.displayName === 'playlist 3')).to.not.be.undefined | ||
270 | }) | ||
271 | |||
272 | it('Should correctly list the playlists', async function () { | ||
273 | this.timeout(30000) | ||
274 | |||
275 | { | ||
276 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, 'createdAt') | ||
277 | |||
278 | expect(res.body.total).to.equal(3) | ||
279 | |||
280 | const data: VideoPlaylist[] = res.body.data | ||
281 | expect(data).to.have.lengthOf(2) | ||
282 | expect(data[ 0 ].displayName).to.equal('playlist 2') | ||
283 | expect(data[ 1 ].displayName).to.equal('playlist 3') | ||
284 | } | ||
285 | |||
286 | { | ||
287 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 1, 2, '-createdAt') | ||
288 | |||
289 | expect(res.body.total).to.equal(3) | ||
290 | |||
291 | const data: VideoPlaylist[] = res.body.data | ||
292 | expect(data).to.have.lengthOf(2) | ||
293 | expect(data[ 0 ].displayName).to.equal('playlist 2') | ||
294 | expect(data[ 1 ].displayName).to.equal('my super playlist') | ||
295 | } | ||
296 | }) | ||
297 | |||
298 | it('Should list video channel playlists', async function () { | ||
299 | this.timeout(30000) | ||
300 | |||
301 | { | ||
302 | const res = await getVideoChannelPlaylistsList(servers[ 0 ].url, 'root_channel', 0, 2, '-createdAt') | ||
303 | |||
304 | expect(res.body.total).to.equal(1) | ||
305 | |||
306 | const data: VideoPlaylist[] = res.body.data | ||
307 | expect(data).to.have.lengthOf(1) | ||
308 | expect(data[ 0 ].displayName).to.equal('my super playlist') | ||
309 | } | ||
310 | }) | ||
311 | |||
312 | it('Should list account playlists', async function () { | ||
313 | this.timeout(30000) | ||
314 | |||
315 | { | ||
316 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, '-createdAt') | ||
317 | |||
318 | expect(res.body.total).to.equal(2) | ||
319 | |||
320 | const data: VideoPlaylist[] = res.body.data | ||
321 | expect(data).to.have.lengthOf(1) | ||
322 | expect(data[ 0 ].displayName).to.equal('playlist 2') | ||
323 | } | ||
324 | |||
325 | { | ||
326 | const res = await getAccountPlaylistsList(servers[ 1 ].url, 'root', 1, 2, 'createdAt') | ||
327 | |||
328 | expect(res.body.total).to.equal(2) | ||
329 | |||
330 | const data: VideoPlaylist[] = res.body.data | ||
331 | expect(data).to.have.lengthOf(1) | ||
332 | expect(data[ 0 ].displayName).to.equal('playlist 3') | ||
333 | } | ||
334 | }) | ||
335 | |||
336 | it('Should not list unlisted or private playlists', async function () { | ||
337 | this.timeout(30000) | ||
338 | |||
339 | await createVideoPlaylist({ | ||
340 | url: servers[ 1 ].url, | ||
341 | token: servers[ 1 ].accessToken, | ||
342 | playlistAttrs: { | ||
343 | displayName: 'playlist unlisted', | ||
344 | privacy: VideoPlaylistPrivacy.UNLISTED | ||
345 | } | ||
346 | }) | ||
347 | |||
348 | await createVideoPlaylist({ | ||
349 | url: servers[ 1 ].url, | ||
350 | token: servers[ 1 ].accessToken, | ||
351 | playlistAttrs: { | ||
352 | displayName: 'playlist private', | ||
353 | privacy: VideoPlaylistPrivacy.PRIVATE | ||
354 | } | ||
355 | }) | ||
356 | |||
357 | await waitJobs(servers) | ||
358 | |||
359 | for (const server of servers) { | ||
360 | const results = [ | ||
361 | await getAccountPlaylistsList(server.url, 'root@localhost:9002', 0, 5, '-createdAt'), | ||
362 | await getVideoPlaylistsList(server.url, 0, 2, '-createdAt') | ||
363 | ] | ||
364 | |||
365 | expect(results[0].body.total).to.equal(2) | ||
366 | expect(results[1].body.total).to.equal(3) | ||
367 | |||
368 | for (const res of results) { | ||
369 | const data: VideoPlaylist[] = res.body.data | ||
370 | expect(data).to.have.lengthOf(2) | ||
371 | expect(data[ 0 ].displayName).to.equal('playlist 3') | ||
372 | expect(data[ 1 ].displayName).to.equal('playlist 2') | ||
373 | } | ||
374 | } | ||
375 | }) | ||
376 | |||
377 | it('Should update a playlist', async function () { | ||
378 | this.timeout(30000) | ||
379 | |||
380 | await updateVideoPlaylist({ | ||
381 | url: servers[1].url, | ||
382 | token: servers[1].accessToken, | ||
383 | playlistAttrs: { | ||
384 | displayName: 'playlist 3 updated', | ||
385 | description: 'description updated', | ||
386 | privacy: VideoPlaylistPrivacy.UNLISTED, | ||
387 | thumbnailfile: 'thumbnail.jpg', | ||
388 | videoChannelId: servers[1].videoChannel.id | ||
389 | }, | ||
390 | playlistId: playlistServer2Id2 | ||
391 | }) | ||
392 | |||
393 | await waitJobs(servers) | ||
394 | |||
395 | for (const server of servers) { | ||
396 | const res = await getVideoPlaylist(server.url, playlistServer2UUID2) | ||
397 | const playlist: VideoPlaylist = res.body | ||
398 | |||
399 | expect(playlist.displayName).to.equal('playlist 3 updated') | ||
400 | expect(playlist.description).to.equal('description updated') | ||
401 | |||
402 | expect(playlist.privacy.id).to.equal(VideoPlaylistPrivacy.UNLISTED) | ||
403 | expect(playlist.privacy.label).to.equal('Unlisted') | ||
404 | |||
405 | expect(playlist.type.id).to.equal(VideoPlaylistType.REGULAR) | ||
406 | expect(playlist.type.label).to.equal('Regular') | ||
407 | |||
408 | expect(playlist.videosLength).to.equal(2) | ||
409 | |||
410 | expect(playlist.ownerAccount.name).to.equal('root') | ||
411 | expect(playlist.ownerAccount.displayName).to.equal('root') | ||
412 | expect(playlist.videoChannel.name).to.equal('root_channel') | ||
413 | expect(playlist.videoChannel.displayName).to.equal('Main root channel') | ||
414 | } | ||
415 | }) | ||
416 | |||
417 | it('Should create a playlist containing different startTimestamp/endTimestamp videos', async function () { | ||
418 | this.timeout(30000) | ||
419 | |||
420 | const addVideo = (elementAttrs: any) => { | ||
421 | return addVideoInPlaylist({ url: servers[0].url, token: servers[0].accessToken, playlistId: playlistServer1Id, elementAttrs }) | ||
422 | } | ||
423 | |||
424 | const res = await createVideoPlaylist({ | ||
425 | url: servers[ 0 ].url, | ||
426 | token: servers[ 0 ].accessToken, | ||
427 | playlistAttrs: { | ||
428 | displayName: 'playlist 4', | ||
429 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
430 | videoChannelId: servers[0].videoChannel.id | ||
431 | } | ||
432 | }) | ||
433 | |||
434 | playlistServer1Id = res.body.videoPlaylist.id | ||
435 | playlistServer1UUID = res.body.videoPlaylist.uuid | ||
436 | |||
437 | await addVideo({ videoId: servers[0].videos[0].uuid, startTimestamp: 15, stopTimestamp: 28 }) | ||
438 | await addVideo({ videoId: servers[2].videos[1].uuid, startTimestamp: 35 }) | ||
439 | await addVideo({ videoId: servers[2].videos[2].uuid }) | ||
440 | await addVideo({ videoId: servers[0].videos[3].uuid, stopTimestamp: 35 }) | ||
441 | await addVideo({ videoId: servers[0].videos[4].uuid, startTimestamp: 45, stopTimestamp: 60 }) | ||
442 | await addVideo({ videoId: nsfwVideoServer1, startTimestamp: 5 }) | ||
443 | |||
444 | await waitJobs(servers) | ||
445 | }) | ||
446 | |||
447 | it('Should correctly list playlist videos', async function () { | ||
448 | this.timeout(30000) | ||
449 | |||
450 | for (const server of servers) { | ||
451 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
452 | |||
453 | expect(res.body.total).to.equal(6) | ||
454 | |||
455 | const videos: Video[] = res.body.data | ||
456 | expect(videos).to.have.lengthOf(6) | ||
457 | |||
458 | expect(videos[0].name).to.equal('video 0 server 1') | ||
459 | expect(videos[0].playlistElement.position).to.equal(1) | ||
460 | expect(videos[0].playlistElement.startTimestamp).to.equal(15) | ||
461 | expect(videos[0].playlistElement.stopTimestamp).to.equal(28) | ||
462 | |||
463 | expect(videos[1].name).to.equal('video 1 server 3') | ||
464 | expect(videos[1].playlistElement.position).to.equal(2) | ||
465 | expect(videos[1].playlistElement.startTimestamp).to.equal(35) | ||
466 | expect(videos[1].playlistElement.stopTimestamp).to.be.null | ||
467 | |||
468 | expect(videos[2].name).to.equal('video 2 server 3') | ||
469 | expect(videos[2].playlistElement.position).to.equal(3) | ||
470 | expect(videos[2].playlistElement.startTimestamp).to.be.null | ||
471 | expect(videos[2].playlistElement.stopTimestamp).to.be.null | ||
472 | |||
473 | expect(videos[3].name).to.equal('video 3 server 1') | ||
474 | expect(videos[3].playlistElement.position).to.equal(4) | ||
475 | expect(videos[3].playlistElement.startTimestamp).to.be.null | ||
476 | expect(videos[3].playlistElement.stopTimestamp).to.equal(35) | ||
477 | |||
478 | expect(videos[4].name).to.equal('video 4 server 1') | ||
479 | expect(videos[4].playlistElement.position).to.equal(5) | ||
480 | expect(videos[4].playlistElement.startTimestamp).to.equal(45) | ||
481 | expect(videos[4].playlistElement.stopTimestamp).to.equal(60) | ||
482 | |||
483 | expect(videos[5].name).to.equal('NSFW video') | ||
484 | expect(videos[5].playlistElement.position).to.equal(6) | ||
485 | expect(videos[5].playlistElement.startTimestamp).to.equal(5) | ||
486 | expect(videos[5].playlistElement.stopTimestamp).to.be.null | ||
487 | |||
488 | const res2 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10, { nsfw: false }) | ||
489 | expect(res2.body.total).to.equal(5) | ||
490 | expect(res2.body.data.find(v => v.name === 'NSFW video')).to.be.undefined | ||
491 | |||
492 | const res3 = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 2) | ||
493 | expect(res3.body.data).to.have.lengthOf(2) | ||
494 | } | ||
495 | }) | ||
496 | |||
497 | it('Should reorder the playlist', async function () { | ||
498 | this.timeout(30000) | ||
499 | |||
500 | { | ||
501 | await reorderVideosPlaylist({ | ||
502 | url: servers[ 0 ].url, | ||
503 | token: servers[ 0 ].accessToken, | ||
504 | playlistId: playlistServer1Id, | ||
505 | elementAttrs: { | ||
506 | startPosition: 2, | ||
507 | insertAfterPosition: 3 | ||
508 | } | ||
509 | }) | ||
510 | |||
511 | await waitJobs(servers) | ||
512 | |||
513 | for (const server of servers) { | ||
514 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
515 | const names = res.body.data.map(v => v.name) | ||
516 | |||
517 | expect(names).to.deep.equal([ | ||
518 | 'video 0 server 1', | ||
519 | 'video 2 server 3', | ||
520 | 'video 1 server 3', | ||
521 | 'video 3 server 1', | ||
522 | 'video 4 server 1', | ||
523 | 'NSFW video' | ||
524 | ]) | ||
525 | } | ||
526 | } | ||
527 | |||
528 | { | ||
529 | await reorderVideosPlaylist({ | ||
530 | url: servers[0].url, | ||
531 | token: servers[0].accessToken, | ||
532 | playlistId: playlistServer1Id, | ||
533 | elementAttrs: { | ||
534 | startPosition: 1, | ||
535 | reorderLength: 3, | ||
536 | insertAfterPosition: 4 | ||
537 | } | ||
538 | }) | ||
539 | |||
540 | await waitJobs(servers) | ||
541 | |||
542 | for (const server of servers) { | ||
543 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
544 | const names = res.body.data.map(v => v.name) | ||
545 | |||
546 | expect(names).to.deep.equal([ | ||
547 | 'video 3 server 1', | ||
548 | 'video 0 server 1', | ||
549 | 'video 2 server 3', | ||
550 | 'video 1 server 3', | ||
551 | 'video 4 server 1', | ||
552 | 'NSFW video' | ||
553 | ]) | ||
554 | } | ||
555 | } | ||
556 | |||
557 | { | ||
558 | await reorderVideosPlaylist({ | ||
559 | url: servers[0].url, | ||
560 | token: servers[0].accessToken, | ||
561 | playlistId: playlistServer1Id, | ||
562 | elementAttrs: { | ||
563 | startPosition: 6, | ||
564 | insertAfterPosition: 3 | ||
565 | } | ||
566 | }) | ||
567 | |||
568 | await waitJobs(servers) | ||
569 | |||
570 | for (const server of servers) { | ||
571 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
572 | const videos: Video[] = res.body.data | ||
573 | |||
574 | const names = videos.map(v => v.name) | ||
575 | |||
576 | expect(names).to.deep.equal([ | ||
577 | 'video 3 server 1', | ||
578 | 'video 0 server 1', | ||
579 | 'video 2 server 3', | ||
580 | 'NSFW video', | ||
581 | 'video 1 server 3', | ||
582 | 'video 4 server 1' | ||
583 | ]) | ||
584 | |||
585 | for (let i = 1; i <= videos.length; i++) { | ||
586 | expect(videos[i - 1].playlistElement.position).to.equal(i) | ||
587 | } | ||
588 | } | ||
589 | } | ||
590 | }) | ||
591 | |||
592 | it('Should update startTimestamp/endTimestamp of some elements', async function () { | ||
593 | this.timeout(30000) | ||
594 | |||
595 | await updateVideoPlaylistElement({ | ||
596 | url: servers[0].url, | ||
597 | token: servers[0].accessToken, | ||
598 | playlistId: playlistServer1Id, | ||
599 | videoId: servers[0].videos[3].uuid, | ||
600 | elementAttrs: { | ||
601 | startTimestamp: 1 | ||
602 | } | ||
603 | }) | ||
604 | |||
605 | await updateVideoPlaylistElement({ | ||
606 | url: servers[0].url, | ||
607 | token: servers[0].accessToken, | ||
608 | playlistId: playlistServer1Id, | ||
609 | videoId: servers[0].videos[4].uuid, | ||
610 | elementAttrs: { | ||
611 | stopTimestamp: null | ||
612 | } | ||
613 | }) | ||
614 | |||
615 | await waitJobs(servers) | ||
616 | |||
617 | for (const server of servers) { | ||
618 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
619 | const videos: Video[] = res.body.data | ||
620 | |||
621 | expect(videos[0].name).to.equal('video 3 server 1') | ||
622 | expect(videos[0].playlistElement.position).to.equal(1) | ||
623 | expect(videos[0].playlistElement.startTimestamp).to.equal(1) | ||
624 | expect(videos[0].playlistElement.stopTimestamp).to.equal(35) | ||
625 | |||
626 | expect(videos[5].name).to.equal('video 4 server 1') | ||
627 | expect(videos[5].playlistElement.position).to.equal(6) | ||
628 | expect(videos[5].playlistElement.startTimestamp).to.equal(45) | ||
629 | expect(videos[5].playlistElement.stopTimestamp).to.be.null | ||
630 | } | ||
631 | }) | ||
632 | |||
633 | it('Should check videos existence in my playlist', async function () { | ||
634 | const videoIds = [ | ||
635 | servers[0].videos[0].id, | ||
636 | 42000, | ||
637 | servers[0].videos[3].id, | ||
638 | 43000, | ||
639 | servers[0].videos[4].id | ||
640 | ] | ||
641 | const res = await doVideosExistInMyPlaylist(servers[ 0 ].url, servers[ 0 ].accessToken, videoIds) | ||
642 | const obj = res.body as VideoExistInPlaylist | ||
643 | |||
644 | { | ||
645 | const elem = obj[servers[0].videos[0].id] | ||
646 | expect(elem).to.have.lengthOf(1) | ||
647 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | ||
648 | expect(elem[ 0 ].startTimestamp).to.equal(15) | ||
649 | expect(elem[ 0 ].stopTimestamp).to.equal(28) | ||
650 | } | ||
651 | |||
652 | { | ||
653 | const elem = obj[servers[0].videos[3].id] | ||
654 | expect(elem).to.have.lengthOf(1) | ||
655 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | ||
656 | expect(elem[ 0 ].startTimestamp).to.equal(1) | ||
657 | expect(elem[ 0 ].stopTimestamp).to.equal(35) | ||
658 | } | ||
659 | |||
660 | { | ||
661 | const elem = obj[servers[0].videos[4].id] | ||
662 | expect(elem).to.have.lengthOf(1) | ||
663 | expect(elem[ 0 ].playlistId).to.equal(playlistServer1Id) | ||
664 | expect(elem[ 0 ].startTimestamp).to.equal(45) | ||
665 | expect(elem[ 0 ].stopTimestamp).to.equal(null) | ||
666 | } | ||
667 | |||
668 | expect(obj[42000]).to.have.lengthOf(0) | ||
669 | expect(obj[43000]).to.have.lengthOf(0) | ||
670 | }) | ||
671 | |||
672 | it('Should automatically update updatedAt field of playlists', async function () { | ||
673 | const server = servers[1] | ||
674 | const videoId = servers[1].videos[5].id | ||
675 | |||
676 | async function getPlaylistNames () { | ||
677 | const res = await getAccountPlaylistsListWithToken(server.url, server.accessToken, 'root', 0, 5, undefined, '-updatedAt') | ||
678 | |||
679 | return (res.body.data as VideoPlaylist[]).map(p => p.displayName) | ||
680 | } | ||
681 | |||
682 | const elementAttrs = { videoId } | ||
683 | await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id1, elementAttrs }) | ||
684 | await addVideoInPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id2, elementAttrs }) | ||
685 | |||
686 | const names1 = await getPlaylistNames() | ||
687 | expect(names1[0]).to.equal('playlist 3 updated') | ||
688 | expect(names1[1]).to.equal('playlist 2') | ||
689 | |||
690 | await removeVideoFromPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id1, videoId }) | ||
691 | |||
692 | const names2 = await getPlaylistNames() | ||
693 | expect(names2[0]).to.equal('playlist 2') | ||
694 | expect(names2[1]).to.equal('playlist 3 updated') | ||
695 | |||
696 | await removeVideoFromPlaylist({ url: server.url, token: server.accessToken, playlistId: playlistServer2Id2, videoId }) | ||
697 | |||
698 | const names3 = await getPlaylistNames() | ||
699 | expect(names3[0]).to.equal('playlist 3 updated') | ||
700 | expect(names3[1]).to.equal('playlist 2') | ||
701 | }) | ||
702 | |||
703 | it('Should delete some elements', async function () { | ||
704 | this.timeout(30000) | ||
705 | |||
706 | await removeVideoFromPlaylist({ | ||
707 | url: servers[0].url, | ||
708 | token: servers[0].accessToken, | ||
709 | playlistId: playlistServer1Id, | ||
710 | videoId: servers[0].videos[3].uuid | ||
711 | }) | ||
712 | |||
713 | await removeVideoFromPlaylist({ | ||
714 | url: servers[0].url, | ||
715 | token: servers[0].accessToken, | ||
716 | playlistId: playlistServer1Id, | ||
717 | videoId: nsfwVideoServer1 | ||
718 | }) | ||
719 | |||
720 | await waitJobs(servers) | ||
721 | |||
722 | for (const server of servers) { | ||
723 | const res = await getPlaylistVideos(server.url, server.accessToken, playlistServer1UUID, 0, 10) | ||
724 | |||
725 | expect(res.body.total).to.equal(4) | ||
726 | |||
727 | const videos: Video[] = res.body.data | ||
728 | expect(videos).to.have.lengthOf(4) | ||
729 | |||
730 | expect(videos[ 0 ].name).to.equal('video 0 server 1') | ||
731 | expect(videos[ 0 ].playlistElement.position).to.equal(1) | ||
732 | |||
733 | expect(videos[ 1 ].name).to.equal('video 2 server 3') | ||
734 | expect(videos[ 1 ].playlistElement.position).to.equal(2) | ||
735 | |||
736 | expect(videos[ 2 ].name).to.equal('video 1 server 3') | ||
737 | expect(videos[ 2 ].playlistElement.position).to.equal(3) | ||
738 | |||
739 | expect(videos[ 3 ].name).to.equal('video 4 server 1') | ||
740 | expect(videos[ 3 ].playlistElement.position).to.equal(4) | ||
741 | } | ||
742 | }) | ||
743 | |||
744 | it('Should delete the playlist on server 1 and delete on server 2 and 3', async function () { | ||
745 | this.timeout(30000) | ||
746 | |||
747 | await deleteVideoPlaylist(servers[0].url, servers[0].accessToken, playlistServer1Id) | ||
748 | |||
749 | await waitJobs(servers) | ||
750 | |||
751 | for (const server of servers) { | ||
752 | await getVideoPlaylist(server.url, playlistServer1UUID, 404) | ||
753 | } | ||
754 | }) | ||
755 | |||
756 | it('Should have deleted the thumbnail on server 1, 2 and 3', async function () { | ||
757 | this.timeout(30000) | ||
758 | |||
759 | for (const server of servers) { | ||
760 | await checkPlaylistFilesWereRemoved(playlistServer1UUID, server.serverNumber) | ||
761 | } | ||
762 | }) | ||
763 | |||
764 | it('Should unfollow servers 1 and 2 and hide their playlists', async function () { | ||
765 | this.timeout(30000) | ||
766 | |||
767 | const finder = data => data.find(p => p.displayName === 'my super playlist') | ||
768 | |||
769 | { | ||
770 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | ||
771 | expect(res.body.total).to.equal(2) | ||
772 | expect(finder(res.body.data)).to.not.be.undefined | ||
773 | } | ||
774 | |||
775 | await unfollow(servers[2].url, servers[2].accessToken, servers[0]) | ||
776 | |||
777 | { | ||
778 | const res = await getVideoPlaylistsList(servers[ 2 ].url, 0, 5) | ||
779 | expect(res.body.total).to.equal(1) | ||
780 | |||
781 | expect(finder(res.body.data)).to.be.undefined | ||
782 | } | ||
783 | }) | ||
784 | |||
785 | it('Should delete a channel and put the associated playlist in private mode', async function () { | ||
786 | this.timeout(30000) | ||
787 | |||
788 | const res = await addVideoChannel(servers[0].url, servers[0].accessToken, { name: 'super_channel', displayName: 'super channel' }) | ||
789 | const videoChannelId = res.body.videoChannel.id | ||
790 | |||
791 | const res2 = await createVideoPlaylist({ | ||
792 | url: servers[0].url, | ||
793 | token: servers[0].accessToken, | ||
794 | playlistAttrs: { | ||
795 | displayName: 'channel playlist', | ||
796 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
797 | videoChannelId | ||
798 | } | ||
799 | }) | ||
800 | const videoPlaylistUUID = res2.body.videoPlaylist.uuid | ||
801 | |||
802 | await waitJobs(servers) | ||
803 | |||
804 | await deleteVideoChannel(servers[0].url, servers[0].accessToken, 'super_channel') | ||
805 | |||
806 | await waitJobs(servers) | ||
807 | |||
808 | const res3 = await getVideoPlaylistWithToken(servers[0].url, servers[0].accessToken, videoPlaylistUUID) | ||
809 | expect(res3.body.displayName).to.equal('channel playlist') | ||
810 | expect(res3.body.privacy.id).to.equal(VideoPlaylistPrivacy.PRIVATE) | ||
811 | |||
812 | await getVideoPlaylist(servers[1].url, videoPlaylistUUID, 404) | ||
813 | }) | ||
814 | |||
815 | it('Should delete an account and delete its playlists', async function () { | ||
816 | this.timeout(30000) | ||
817 | |||
818 | const user = { username: 'user_1', password: 'password' } | ||
819 | const res = await createUser({ | ||
820 | url: servers[ 0 ].url, | ||
821 | accessToken: servers[ 0 ].accessToken, | ||
822 | username: user.username, | ||
823 | password: user.password | ||
824 | }) | ||
825 | |||
826 | const userId = res.body.user.id | ||
827 | const userAccessToken = await userLogin(servers[0], user) | ||
828 | |||
829 | const resChannel = await getMyUserInformation(servers[0].url, userAccessToken) | ||
830 | const userChannel = (resChannel.body as User).videoChannels[0] | ||
831 | |||
832 | await createVideoPlaylist({ | ||
833 | url: servers[0].url, | ||
834 | token: userAccessToken, | ||
835 | playlistAttrs: { | ||
836 | displayName: 'playlist to be deleted', | ||
837 | privacy: VideoPlaylistPrivacy.PUBLIC, | ||
838 | videoChannelId: userChannel.id | ||
839 | } | ||
840 | }) | ||
841 | |||
842 | await waitJobs(servers) | ||
843 | |||
844 | const finder = data => data.find(p => p.displayName === 'playlist to be deleted') | ||
845 | |||
846 | { | ||
847 | for (const server of [ servers[0], servers[1] ]) { | ||
848 | const res = await getVideoPlaylistsList(server.url, 0, 15) | ||
849 | expect(finder(res.body.data)).to.not.be.undefined | ||
850 | } | ||
851 | } | ||
852 | |||
853 | await removeUser(servers[0].url, userId, servers[0].accessToken) | ||
854 | await waitJobs(servers) | ||
855 | |||
856 | { | ||
857 | for (const server of [ servers[0], servers[1] ]) { | ||
858 | const res = await getVideoPlaylistsList(server.url, 0, 15) | ||
859 | expect(finder(res.body.data)).to.be.undefined | ||
860 | } | ||
861 | } | ||
862 | }) | ||
863 | |||
864 | after(async function () { | ||
865 | await cleanupTests(servers) | ||
866 | }) | ||
867 | }) | ||
diff --git a/server/tests/api/videos/video-privacy.ts b/server/tests/api/videos/video-privacy.ts index 0b4e66369..ef1cf0f07 100644 --- a/server/tests/api/videos/video-privacy.ts +++ b/server/tests/api/videos/video-privacy.ts | |||
@@ -4,18 +4,19 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' | 5 | import { VideoPrivacy } from '../../../../shared/models/videos/video-privacy.enum' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | flushAndRunMultipleServers, | 8 | flushAndRunMultipleServers, |
8 | getVideosList, | 9 | getVideosList, |
9 | killallServers, | 10 | killallServers, |
10 | ServerInfo, | 11 | ServerInfo, |
11 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
12 | uploadVideo | 13 | uploadVideo |
13 | } from '../../../../shared/utils/index' | 14 | } from '../../../../shared/extra-utils/index' |
14 | import { doubleFollow } from '../../../../shared/utils/server/follows' | 15 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
15 | import { userLogin } from '../../../../shared/utils/users/login' | 16 | import { userLogin } from '../../../../shared/extra-utils/users/login' |
16 | import { createUser } from '../../../../shared/utils/users/users' | 17 | import { createUser } from '../../../../shared/extra-utils/users/users' |
17 | import { getMyVideos, getVideo, getVideoWithToken, updateVideo } from '../../../../shared/utils/videos/videos' | 18 | import { getMyVideos, getVideo, getVideoWithToken, updateVideo } from '../../../../shared/extra-utils/videos/videos' |
18 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
19 | 20 | ||
20 | const expect = chai.expect | 21 | const expect = chai.expect |
21 | 22 | ||
@@ -78,7 +79,7 @@ describe('Test video privacy', function () { | |||
78 | username: 'hello', | 79 | username: 'hello', |
79 | password: 'super password' | 80 | password: 'super password' |
80 | } | 81 | } |
81 | await createUser(servers[0].url, servers[0].accessToken, user.username, user.password) | 82 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: user.username, password: user.password }) |
82 | 83 | ||
83 | const token = await userLogin(servers[0], user) | 84 | const token = await userLogin(servers[0], user) |
84 | await getVideoWithToken(servers[0].url, token, privateVideoUUID, 403) | 85 | await getVideoWithToken(servers[0].url, token, privateVideoUUID, 403) |
@@ -153,6 +154,6 @@ describe('Test video privacy', function () { | |||
153 | }) | 154 | }) |
154 | 155 | ||
155 | after(async function () { | 156 | after(async function () { |
156 | killallServers(servers) | 157 | await cleanupTests(servers) |
157 | }) | 158 | }) |
158 | }) | 159 | }) |
diff --git a/server/tests/api/videos/video-schedule-update.ts b/server/tests/api/videos/video-schedule-update.ts index 632c4244c..64f657780 100644 --- a/server/tests/api/videos/video-schedule-update.ts +++ b/server/tests/api/videos/video-schedule-update.ts | |||
@@ -4,6 +4,7 @@ import * as chai from 'chai' | |||
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { VideoPrivacy } from '../../../../shared/models/videos' | 5 | import { VideoPrivacy } from '../../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
9 | getMyVideos, | 10 | getMyVideos, |
@@ -15,8 +16,8 @@ import { | |||
15 | updateVideo, | 16 | updateVideo, |
16 | uploadVideo, | 17 | uploadVideo, |
17 | wait | 18 | wait |
18 | } from '../../../../shared/utils' | 19 | } from '../../../../shared/extra-utils' |
19 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 20 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
20 | 21 | ||
21 | const expect = chai.expect | 22 | const expect = chai.expect |
22 | 23 | ||
@@ -166,6 +167,6 @@ describe('Test video update scheduler', function () { | |||
166 | }) | 167 | }) |
167 | 168 | ||
168 | after(async function () { | 169 | after(async function () { |
169 | killallServers(servers) | 170 | await cleanupTests(servers) |
170 | }) | 171 | }) |
171 | }) | 172 | }) |
diff --git a/server/tests/api/videos/video-transcoder.ts b/server/tests/api/videos/video-transcoder.ts index eefd32ef8..3cd43e99b 100644 --- a/server/tests/api/videos/video-transcoder.ts +++ b/server/tests/api/videos/video-transcoder.ts | |||
@@ -6,7 +6,7 @@ import { omit } from 'lodash' | |||
6 | import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos' | 6 | import { getMaxBitrate, VideoDetails, VideoResolution, VideoState } from '../../../../shared/models/videos' |
7 | import { audio, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' | 7 | import { audio, getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg-utils' |
8 | import { | 8 | import { |
9 | buildAbsoluteFixturePath, | 9 | buildAbsoluteFixturePath, cleanupTests, |
10 | doubleFollow, | 10 | doubleFollow, |
11 | flushAndRunMultipleServers, | 11 | flushAndRunMultipleServers, |
12 | generateHighBitrateVideo, | 12 | generateHighBitrateVideo, |
@@ -19,9 +19,9 @@ import { | |||
19 | setAccessTokensToServers, | 19 | setAccessTokensToServers, |
20 | uploadVideo, | 20 | uploadVideo, |
21 | webtorrentAdd | 21 | webtorrentAdd |
22 | } from '../../../../shared/utils' | 22 | } from '../../../../shared/extra-utils' |
23 | import { extname, join } from 'path' | 23 | import { extname, join } from 'path' |
24 | import { waitJobs } from '../../../../shared/utils/server/jobs' | 24 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
25 | import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' | 25 | import { VIDEO_TRANSCODING_FPS } from '../../../../server/initializers/constants' |
26 | 26 | ||
27 | const expect = chai.expect | 27 | const expect = chai.expect |
@@ -350,6 +350,6 @@ describe('Test video transcoding', function () { | |||
350 | }) | 350 | }) |
351 | 351 | ||
352 | after(async function () { | 352 | after(async function () { |
353 | killallServers(servers) | 353 | await cleanupTests(servers) |
354 | }) | 354 | }) |
355 | }) | 355 | }) |
diff --git a/server/tests/api/videos/videos-filter.ts b/server/tests/api/videos/videos-filter.ts index 59e37ad86..e1e65260f 100644 --- a/server/tests/api/videos/videos-filter.ts +++ b/server/tests/api/videos/videos-filter.ts | |||
@@ -3,6 +3,7 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
@@ -13,7 +14,7 @@ import { | |||
13 | setAccessTokensToServers, | 14 | setAccessTokensToServers, |
14 | uploadVideo, | 15 | uploadVideo, |
15 | userLogin | 16 | userLogin |
16 | } from '../../../../shared/utils' | 17 | } from '../../../../shared/extra-utils' |
17 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' | 18 | import { Video, VideoPrivacy } from '../../../../shared/models/videos' |
18 | import { UserRole } from '../../../../shared/models/users' | 19 | import { UserRole } from '../../../../shared/models/users' |
19 | 20 | ||
@@ -55,8 +56,6 @@ describe('Test videos filter validator', function () { | |||
55 | before(async function () { | 56 | before(async function () { |
56 | this.timeout(120000) | 57 | this.timeout(120000) |
57 | 58 | ||
58 | await flushTests() | ||
59 | |||
60 | servers = await flushAndRunMultipleServers(2) | 59 | servers = await flushAndRunMultipleServers(2) |
61 | 60 | ||
62 | await setAccessTokensToServers(servers) | 61 | await setAccessTokensToServers(servers) |
@@ -64,13 +63,15 @@ describe('Test videos filter validator', function () { | |||
64 | for (const server of servers) { | 63 | for (const server of servers) { |
65 | const moderator = { username: 'moderator', password: 'my super password' } | 64 | const moderator = { username: 'moderator', password: 'my super password' } |
66 | await createUser( | 65 | await createUser( |
67 | server.url, | 66 | { |
68 | server.accessToken, | 67 | url: server.url, |
69 | moderator.username, | 68 | accessToken: server.accessToken, |
70 | moderator.password, | 69 | username: moderator.username, |
71 | undefined, | 70 | password: moderator.password, |
72 | undefined, | 71 | videoQuota: undefined, |
73 | UserRole.MODERATOR | 72 | videoQuotaDaily: undefined, |
73 | role: UserRole.MODERATOR | ||
74 | } | ||
74 | ) | 75 | ) |
75 | server['moderatorAccessToken'] = await userLogin(server, moderator) | 76 | server['moderatorAccessToken'] = await userLogin(server, moderator) |
76 | 77 | ||
@@ -120,11 +121,6 @@ describe('Test videos filter validator', function () { | |||
120 | }) | 121 | }) |
121 | 122 | ||
122 | after(async function () { | 123 | after(async function () { |
123 | killallServers(servers) | 124 | await cleanupTests(servers) |
124 | |||
125 | // Keep the logs if the test failed | ||
126 | if (this['ok']) { | ||
127 | await flushTests() | ||
128 | } | ||
129 | }) | 125 | }) |
130 | }) | 126 | }) |
diff --git a/server/tests/api/videos/videos-history.ts b/server/tests/api/videos/videos-history.ts index f654a422b..c7e55c1ab 100644 --- a/server/tests/api/videos/videos-history.ts +++ b/server/tests/api/videos/videos-history.ts | |||
@@ -3,21 +3,23 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | flushTests, | 8 | flushAndRunServer, |
8 | getVideosListWithToken, | 9 | getVideosListWithToken, |
9 | getVideoWithToken, | 10 | getVideoWithToken, |
10 | killallServers, | 11 | killallServers, |
11 | runServer, | 12 | reRunServer, |
12 | searchVideoWithToken, | 13 | searchVideoWithToken, |
13 | ServerInfo, | 14 | ServerInfo, |
14 | setAccessTokensToServers, | 15 | setAccessTokensToServers, |
15 | updateMyUser, | 16 | updateMyUser, |
16 | uploadVideo, | 17 | uploadVideo, |
17 | userLogin | 18 | userLogin, |
18 | } from '../../../../shared/utils' | 19 | wait |
20 | } from '../../../../shared/extra-utils' | ||
19 | import { Video, VideoDetails } from '../../../../shared/models/videos' | 21 | import { Video, VideoDetails } from '../../../../shared/models/videos' |
20 | import { listMyVideosHistory, removeMyVideosHistory, userWatchVideo } from '../../../../shared/utils/videos/video-history' | 22 | import { listMyVideosHistory, removeMyVideosHistory, userWatchVideo } from '../../../../shared/extra-utils/videos/video-history' |
21 | 23 | ||
22 | const expect = chai.expect | 24 | const expect = chai.expect |
23 | 25 | ||
@@ -32,9 +34,7 @@ describe('Test videos history', function () { | |||
32 | before(async function () { | 34 | before(async function () { |
33 | this.timeout(30000) | 35 | this.timeout(30000) |
34 | 36 | ||
35 | await flushTests() | 37 | server = await flushAndRunServer(1) |
36 | |||
37 | server = await runServer(1) | ||
38 | 38 | ||
39 | await setAccessTokensToServers([ server ]) | 39 | await setAccessTokensToServers([ server ]) |
40 | 40 | ||
@@ -57,7 +57,7 @@ describe('Test videos history', function () { | |||
57 | username: 'user_1', | 57 | username: 'user_1', |
58 | password: 'super password' | 58 | password: 'super password' |
59 | } | 59 | } |
60 | await createUser(server.url, server.accessToken, user.username, user.password) | 60 | await createUser({ url: server.url, accessToken: server.accessToken, username: user.username, password: user.password }) |
61 | userAccessToken = await userLogin(server, user) | 61 | userAccessToken = await userLogin(server, user) |
62 | }) | 62 | }) |
63 | 63 | ||
@@ -192,12 +192,36 @@ describe('Test videos history', function () { | |||
192 | expect(videos[1].name).to.equal('video 3') | 192 | expect(videos[1].name).to.equal('video 3') |
193 | }) | 193 | }) |
194 | 194 | ||
195 | after(async function () { | 195 | it('Should not clean old history', async function () { |
196 | this.timeout(50000) | ||
197 | |||
196 | killallServers([ server ]) | 198 | killallServers([ server ]) |
197 | 199 | ||
198 | // Keep the logs if the test failed | 200 | await reRunServer(server, { history: { videos: { max_age: '10 days' } } }) |
199 | if (this['ok']) { | 201 | |
200 | await flushTests() | 202 | await wait(6000) |
201 | } | 203 | |
204 | // Should still have history | ||
205 | |||
206 | const res = await listMyVideosHistory(server.url, server.accessToken) | ||
207 | |||
208 | expect(res.body.total).to.equal(2) | ||
209 | }) | ||
210 | |||
211 | it('Should clean old history', async function () { | ||
212 | this.timeout(50000) | ||
213 | |||
214 | killallServers([ server ]) | ||
215 | |||
216 | await reRunServer(server, { history: { videos: { max_age: '5 seconds' } } }) | ||
217 | |||
218 | await wait(6000) | ||
219 | |||
220 | const res = await listMyVideosHistory(server.url, server.accessToken) | ||
221 | expect(res.body.total).to.equal(0) | ||
222 | }) | ||
223 | |||
224 | after(async function () { | ||
225 | await cleanupTests([ server ]) | ||
202 | }) | 226 | }) |
203 | }) | 227 | }) |
diff --git a/server/tests/api/videos/videos-overview.ts b/server/tests/api/videos/videos-overview.ts index 7221bcae6..975a5c87a 100644 --- a/server/tests/api/videos/videos-overview.ts +++ b/server/tests/api/videos/videos-overview.ts | |||
@@ -2,8 +2,8 @@ | |||
2 | 2 | ||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { flushTests, killallServers, runServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../../../shared/utils' | 5 | import { cleanupTests, flushAndRunServer, ServerInfo, setAccessTokensToServers, uploadVideo } from '../../../../shared/extra-utils' |
6 | import { getVideosOverview } from '../../../../shared/utils/overviews/overviews' | 6 | import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews' |
7 | import { VideosOverview } from '../../../../shared/models/overviews' | 7 | import { VideosOverview } from '../../../../shared/models/overviews' |
8 | 8 | ||
9 | const expect = chai.expect | 9 | const expect = chai.expect |
@@ -14,9 +14,7 @@ describe('Test a videos overview', function () { | |||
14 | before(async function () { | 14 | before(async function () { |
15 | this.timeout(30000) | 15 | this.timeout(30000) |
16 | 16 | ||
17 | await flushTests() | 17 | server = await flushAndRunServer(1) |
18 | |||
19 | server = await runServer(1) | ||
20 | 18 | ||
21 | await setAccessTokensToServers([ server ]) | 19 | await setAccessTokensToServers([ server ]) |
22 | }) | 20 | }) |
@@ -90,11 +88,6 @@ describe('Test a videos overview', function () { | |||
90 | }) | 88 | }) |
91 | 89 | ||
92 | after(async function () { | 90 | after(async function () { |
93 | killallServers([ server ]) | 91 | await cleanupTests([ server ]) |
94 | |||
95 | // Keep the logs if the test failed | ||
96 | if (this['ok']) { | ||
97 | await flushTests() | ||
98 | } | ||
99 | }) | 92 | }) |
100 | }) | 93 | }) |
diff --git a/server/tests/api/videos/videos-views-cleaner.ts b/server/tests/api/videos/videos-views-cleaner.ts new file mode 100644 index 000000000..c21d46d56 --- /dev/null +++ b/server/tests/api/videos/videos-views-cleaner.ts | |||
@@ -0,0 +1,106 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | flushAndRunMultipleServers, | ||
7 | flushTests, | ||
8 | killallServers, | ||
9 | reRunServer, | ||
10 | flushAndRunServer, | ||
11 | ServerInfo, | ||
12 | setAccessTokensToServers, | ||
13 | uploadVideo, uploadVideoAndGetId, viewVideo, wait, countVideoViewsOf, doubleFollow, waitJobs, cleanupTests | ||
14 | } from '../../../../shared/extra-utils' | ||
15 | import { getVideosOverview } from '../../../../shared/extra-utils/overviews/overviews' | ||
16 | import { VideosOverview } from '../../../../shared/models/overviews' | ||
17 | import { listMyVideosHistory } from '../../../../shared/extra-utils/videos/video-history' | ||
18 | |||
19 | const expect = chai.expect | ||
20 | |||
21 | describe('Test video views cleaner', function () { | ||
22 | let servers: ServerInfo[] | ||
23 | |||
24 | let videoIdServer1: string | ||
25 | let videoIdServer2: string | ||
26 | |||
27 | before(async function () { | ||
28 | this.timeout(50000) | ||
29 | |||
30 | servers = await flushAndRunMultipleServers(2) | ||
31 | await setAccessTokensToServers(servers) | ||
32 | |||
33 | await doubleFollow(servers[0], servers[1]) | ||
34 | |||
35 | videoIdServer1 = (await uploadVideoAndGetId({ server: servers[0], videoName: 'video server 1' })).uuid | ||
36 | videoIdServer2 = (await uploadVideoAndGetId({ server: servers[1], videoName: 'video server 2' })).uuid | ||
37 | |||
38 | await waitJobs(servers) | ||
39 | |||
40 | await viewVideo(servers[0].url, videoIdServer1) | ||
41 | await viewVideo(servers[1].url, videoIdServer1) | ||
42 | await viewVideo(servers[0].url, videoIdServer2) | ||
43 | await viewVideo(servers[1].url, videoIdServer2) | ||
44 | |||
45 | await waitJobs(servers) | ||
46 | }) | ||
47 | |||
48 | it('Should not clean old video views', async function () { | ||
49 | this.timeout(50000) | ||
50 | |||
51 | killallServers([ servers[0] ]) | ||
52 | |||
53 | await reRunServer(servers[0], { views: { videos: { remote: { max_age: '10 days' } } } }) | ||
54 | |||
55 | await wait(6000) | ||
56 | |||
57 | // Should still have views | ||
58 | |||
59 | { | ||
60 | for (const server of servers) { | ||
61 | const total = await countVideoViewsOf(server.serverNumber, videoIdServer1) | ||
62 | expect(total).to.equal(2) | ||
63 | } | ||
64 | } | ||
65 | |||
66 | { | ||
67 | for (const server of servers) { | ||
68 | const total = await countVideoViewsOf(server.serverNumber, videoIdServer2) | ||
69 | expect(total).to.equal(2) | ||
70 | } | ||
71 | } | ||
72 | }) | ||
73 | |||
74 | it('Should clean old video views', async function () { | ||
75 | this.timeout(50000) | ||
76 | |||
77 | this.timeout(50000) | ||
78 | |||
79 | killallServers([ servers[0] ]) | ||
80 | |||
81 | await reRunServer(servers[0], { views: { videos: { remote: { max_age: '5 seconds' } } } }) | ||
82 | |||
83 | await wait(6000) | ||
84 | |||
85 | // Should still have views | ||
86 | |||
87 | { | ||
88 | for (const server of servers) { | ||
89 | const total = await countVideoViewsOf(server.serverNumber, videoIdServer1) | ||
90 | expect(total).to.equal(2) | ||
91 | } | ||
92 | } | ||
93 | |||
94 | { | ||
95 | const totalServer1 = await countVideoViewsOf(servers[0].serverNumber, videoIdServer2) | ||
96 | expect(totalServer1).to.equal(0) | ||
97 | |||
98 | const totalServer2 = await countVideoViewsOf(servers[1].serverNumber, videoIdServer2) | ||
99 | expect(totalServer2).to.equal(2) | ||
100 | } | ||
101 | }) | ||
102 | |||
103 | after(async function () { | ||
104 | await cleanupTests(servers) | ||
105 | }) | ||
106 | }) | ||
diff --git a/server/tests/cli/create-import-video-file-job.ts b/server/tests/cli/create-import-video-file-job.ts index 4acda47b1..0d378c1aa 100644 --- a/server/tests/cli/create-import-video-file-job.ts +++ b/server/tests/cli/create-import-video-file-job.ts | |||
@@ -4,6 +4,7 @@ import 'mocha' | |||
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { VideoDetails, VideoFile } from '../../../shared/models/videos' | 5 | import { VideoDetails, VideoFile } from '../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | doubleFollow, | 8 | doubleFollow, |
8 | execCLI, | 9 | execCLI, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
@@ -15,8 +16,8 @@ import { | |||
15 | ServerInfo, | 16 | ServerInfo, |
16 | setAccessTokensToServers, | 17 | setAccessTokensToServers, |
17 | uploadVideo | 18 | uploadVideo |
18 | } from '../../../shared/utils' | 19 | } from '../../../shared/extra-utils' |
19 | import { waitJobs } from '../../../shared/utils/server/jobs' | 20 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
20 | 21 | ||
21 | const expect = chai.expect | 22 | const expect = chai.expect |
22 | 23 | ||
@@ -39,7 +40,6 @@ describe('Test create import video jobs', function () { | |||
39 | 40 | ||
40 | before(async function () { | 41 | before(async function () { |
41 | this.timeout(90000) | 42 | this.timeout(90000) |
42 | await flushTests() | ||
43 | 43 | ||
44 | // Run server 2 to have transcoding enabled | 44 | // Run server 2 to have transcoding enabled |
45 | servers = await flushAndRunMultipleServers(2) | 45 | servers = await flushAndRunMultipleServers(2) |
@@ -132,6 +132,6 @@ describe('Test create import video jobs', function () { | |||
132 | }) | 132 | }) |
133 | 133 | ||
134 | after(async function () { | 134 | after(async function () { |
135 | killallServers(servers) | 135 | await cleanupTests(servers) |
136 | }) | 136 | }) |
137 | }) | 137 | }) |
diff --git a/server/tests/cli/create-transcoding-job.ts b/server/tests/cli/create-transcoding-job.ts index 50be5fa19..1c0e10066 100644 --- a/server/tests/cli/create-transcoding-job.ts +++ b/server/tests/cli/create-transcoding-job.ts | |||
@@ -4,6 +4,7 @@ import 'mocha' | |||
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { VideoDetails } from '../../../shared/models/videos' | 5 | import { VideoDetails } from '../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | doubleFollow, | 8 | doubleFollow, |
8 | execCLI, | 9 | execCLI, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
@@ -15,8 +16,8 @@ import { | |||
15 | ServerInfo, | 16 | ServerInfo, |
16 | setAccessTokensToServers, | 17 | setAccessTokensToServers, |
17 | uploadVideo, wait | 18 | uploadVideo, wait |
18 | } from '../../../shared/utils' | 19 | } from '../../../shared/extra-utils' |
19 | import { waitJobs } from '../../../shared/utils/server/jobs' | 20 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
20 | 21 | ||
21 | const expect = chai.expect | 22 | const expect = chai.expect |
22 | 23 | ||
@@ -28,8 +29,6 @@ describe('Test create transcoding jobs', function () { | |||
28 | before(async function () { | 29 | before(async function () { |
29 | this.timeout(60000) | 30 | this.timeout(60000) |
30 | 31 | ||
31 | await flushTests() | ||
32 | |||
33 | // Run server 2 to have transcoding enabled | 32 | // Run server 2 to have transcoding enabled |
34 | servers = await flushAndRunMultipleServers(2) | 33 | servers = await flushAndRunMultipleServers(2) |
35 | await setAccessTokensToServers(servers) | 34 | await setAccessTokensToServers(servers) |
@@ -127,6 +126,6 @@ describe('Test create transcoding jobs', function () { | |||
127 | }) | 126 | }) |
128 | 127 | ||
129 | after(async function () { | 128 | after(async function () { |
130 | killallServers(servers) | 129 | await cleanupTests(servers) |
131 | }) | 130 | }) |
132 | }) | 131 | }) |
diff --git a/server/tests/cli/optimize-old-videos.ts b/server/tests/cli/optimize-old-videos.ts index 6f6bc25a6..5e12c0089 100644 --- a/server/tests/cli/optimize-old-videos.ts +++ b/server/tests/cli/optimize-old-videos.ts | |||
@@ -4,6 +4,7 @@ import 'mocha' | |||
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { getMaxBitrate, Video, VideoDetails, VideoResolution } from '../../../shared/models/videos' | 5 | import { getMaxBitrate, Video, VideoDetails, VideoResolution } from '../../../shared/models/videos' |
6 | import { | 6 | import { |
7 | cleanupTests, | ||
7 | doubleFollow, | 8 | doubleFollow, |
8 | execCLI, | 9 | execCLI, |
9 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
@@ -15,10 +16,10 @@ import { | |||
15 | ServerInfo, | 16 | ServerInfo, |
16 | setAccessTokensToServers, | 17 | setAccessTokensToServers, |
17 | uploadVideo, viewVideo, wait | 18 | uploadVideo, viewVideo, wait |
18 | } from '../../../shared/utils' | 19 | } from '../../../shared/extra-utils' |
19 | import { waitJobs } from '../../../shared/utils/server/jobs' | 20 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
20 | import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../helpers/ffmpeg-utils' | 21 | import { getVideoFileBitrate, getVideoFileFPS, getVideoFileResolution } from '../../helpers/ffmpeg-utils' |
21 | import { VIDEO_TRANSCODING_FPS } from '../../initializers' | 22 | import { VIDEO_TRANSCODING_FPS } from '../../initializers/constants' |
22 | import { join } from 'path' | 23 | import { join } from 'path' |
23 | 24 | ||
24 | const expect = chai.expect | 25 | const expect = chai.expect |
@@ -31,8 +32,6 @@ describe('Test optimize old videos', function () { | |||
31 | before(async function () { | 32 | before(async function () { |
32 | this.timeout(200000) | 33 | this.timeout(200000) |
33 | 34 | ||
34 | await flushTests() | ||
35 | |||
36 | // Run server 2 to have transcoding enabled | 35 | // Run server 2 to have transcoding enabled |
37 | servers = await flushAndRunMultipleServers(2) | 36 | servers = await flushAndRunMultipleServers(2) |
38 | await setAccessTokensToServers(servers) | 37 | await setAccessTokensToServers(servers) |
@@ -115,6 +114,6 @@ describe('Test optimize old videos', function () { | |||
115 | }) | 114 | }) |
116 | 115 | ||
117 | after(async function () { | 116 | after(async function () { |
118 | killallServers(servers) | 117 | await cleanupTests(servers) |
119 | }) | 118 | }) |
120 | }) | 119 | }) |
diff --git a/server/tests/cli/peertube.ts b/server/tests/cli/peertube.ts index e2836d0c3..80bbc98d5 100644 --- a/server/tests/cli/peertube.ts +++ b/server/tests/cli/peertube.ts | |||
@@ -8,10 +8,10 @@ import { | |||
8 | flushTests, | 8 | flushTests, |
9 | getEnvCli, | 9 | getEnvCli, |
10 | killallServers, | 10 | killallServers, |
11 | runServer, | 11 | flushAndRunServer, |
12 | ServerInfo, | 12 | ServerInfo, |
13 | setAccessTokensToServers | 13 | setAccessTokensToServers, cleanupTests |
14 | } from '../../../shared/utils' | 14 | } from '../../../shared/extra-utils' |
15 | 15 | ||
16 | describe('Test CLI wrapper', function () { | 16 | describe('Test CLI wrapper', function () { |
17 | let server: ServerInfo | 17 | let server: ServerInfo |
@@ -19,12 +19,10 @@ describe('Test CLI wrapper', function () { | |||
19 | 19 | ||
20 | before(async function () { | 20 | before(async function () { |
21 | this.timeout(30000) | 21 | this.timeout(30000) |
22 | 22 | server = await flushAndRunServer(1) | |
23 | await flushTests() | ||
24 | server = await runServer(1) | ||
25 | await setAccessTokensToServers([ server ]) | 23 | await setAccessTokensToServers([ server ]) |
26 | 24 | ||
27 | await createUser(server.url, server.accessToken, 'user_1', 'super password') | 25 | await createUser({ url: server.url, accessToken: server.accessToken, username: 'user_1', password: 'super password' }) |
28 | }) | 26 | }) |
29 | 27 | ||
30 | it('Should display no selected instance', async function () { | 28 | it('Should display no selected instance', async function () { |
@@ -48,6 +46,6 @@ describe('Test CLI wrapper', function () { | |||
48 | 46 | ||
49 | await execCLI(cmd + ` auth del ${server.url}`) | 47 | await execCLI(cmd + ` auth del ${server.url}`) |
50 | 48 | ||
51 | killallServers([ server ]) | 49 | await cleanupTests([ server ]) |
52 | }) | 50 | }) |
53 | }) | 51 | }) |
diff --git a/server/tests/cli/reset-password.ts b/server/tests/cli/reset-password.ts index 1b65f7e39..6abb6738f 100644 --- a/server/tests/cli/reset-password.ts +++ b/server/tests/cli/reset-password.ts | |||
@@ -1,28 +1,25 @@ | |||
1 | import 'mocha' | 1 | import 'mocha' |
2 | 2 | ||
3 | import { | 3 | import { |
4 | cleanupTests, | ||
4 | createUser, | 5 | createUser, |
5 | execCLI, | 6 | execCLI, |
6 | flushTests, | 7 | flushAndRunServer, |
7 | getEnvCli, | 8 | getEnvCli, |
8 | killallServers, | ||
9 | login, | 9 | login, |
10 | runServer, | ||
11 | ServerInfo, | 10 | ServerInfo, |
12 | setAccessTokensToServers | 11 | setAccessTokensToServers |
13 | } from '../../../shared/utils' | 12 | } from '../../../shared/extra-utils' |
14 | 13 | ||
15 | describe('Test reset password scripts', function () { | 14 | describe('Test reset password scripts', function () { |
16 | let server: ServerInfo | 15 | let server: ServerInfo |
17 | 16 | ||
18 | before(async function () { | 17 | before(async function () { |
19 | this.timeout(30000) | 18 | this.timeout(30000) |
20 | 19 | server = await flushAndRunServer(1) | |
21 | await flushTests() | ||
22 | server = await runServer(1) | ||
23 | await setAccessTokensToServers([ server ]) | 20 | await setAccessTokensToServers([ server ]) |
24 | 21 | ||
25 | await createUser(server.url, server.accessToken, 'user_1', 'super password') | 22 | await createUser({ url: server.url, accessToken: server.accessToken, username: 'user_1', password: 'super password' }) |
26 | }) | 23 | }) |
27 | 24 | ||
28 | it('Should change the user password from CLI', async function () { | 25 | it('Should change the user password from CLI', async function () { |
@@ -35,6 +32,6 @@ describe('Test reset password scripts', function () { | |||
35 | }) | 32 | }) |
36 | 33 | ||
37 | after(async function () { | 34 | after(async function () { |
38 | killallServers([ server ]) | 35 | await cleanupTests([ server ]) |
39 | }) | 36 | }) |
40 | }) | 37 | }) |
diff --git a/server/tests/cli/update-host.ts b/server/tests/cli/update-host.ts index 811ea6a9f..55c43b32f 100644 --- a/server/tests/cli/update-host.ts +++ b/server/tests/cli/update-host.ts | |||
@@ -3,26 +3,26 @@ | |||
3 | import 'mocha' | 3 | import 'mocha' |
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { VideoDetails } from '../../../shared/models/videos' | 5 | import { VideoDetails } from '../../../shared/models/videos' |
6 | import { waitJobs } from '../../../shared/utils/server/jobs' | 6 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
7 | import { addVideoCommentThread } from '../../../shared/utils/videos/video-comments' | 7 | import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' |
8 | import { | 8 | import { |
9 | addVideoChannel, | 9 | addVideoChannel, |
10 | cleanupTests, | ||
10 | createUser, | 11 | createUser, |
11 | execCLI, | 12 | execCLI, |
12 | flushTests, | 13 | flushAndRunServer, |
13 | getEnvCli, | 14 | getEnvCli, |
14 | getVideo, | 15 | getVideo, |
15 | getVideoChannelsList, | 16 | getVideoChannelsList, |
16 | getVideosList, | 17 | getVideosList, |
17 | killallServers, | 18 | killallServers, |
18 | makeActivityPubGetRequest, | 19 | makeActivityPubGetRequest, |
19 | parseTorrentVideo, | 20 | parseTorrentVideo, reRunServer, |
20 | runServer, | ||
21 | ServerInfo, | 21 | ServerInfo, |
22 | setAccessTokensToServers, | 22 | setAccessTokensToServers, |
23 | uploadVideo | 23 | uploadVideo |
24 | } from '../../../shared/utils' | 24 | } from '../../../shared/extra-utils' |
25 | import { getAccountsList } from '../../../shared/utils/users/accounts' | 25 | import { getAccountsList } from '../../../shared/extra-utils/users/accounts' |
26 | 26 | ||
27 | const expect = chai.expect | 27 | const expect = chai.expect |
28 | 28 | ||
@@ -32,15 +32,13 @@ describe('Test update host scripts', function () { | |||
32 | before(async function () { | 32 | before(async function () { |
33 | this.timeout(60000) | 33 | this.timeout(60000) |
34 | 34 | ||
35 | await flushTests() | ||
36 | |||
37 | const overrideConfig = { | 35 | const overrideConfig = { |
38 | webserver: { | 36 | webserver: { |
39 | port: 9256 | 37 | port: 9256 |
40 | } | 38 | } |
41 | } | 39 | } |
42 | // Run server 2 to have transcoding enabled | 40 | // Run server 2 to have transcoding enabled |
43 | server = await runServer(2, overrideConfig) | 41 | server = await flushAndRunServer(2, overrideConfig) |
44 | await setAccessTokensToServers([ server ]) | 42 | await setAccessTokensToServers([ server ]) |
45 | 43 | ||
46 | // Upload two videos for our needs | 44 | // Upload two videos for our needs |
@@ -50,7 +48,7 @@ describe('Test update host scripts', function () { | |||
50 | await uploadVideo(server.url, server.accessToken, videoAttributes) | 48 | await uploadVideo(server.url, server.accessToken, videoAttributes) |
51 | 49 | ||
52 | // Create a user | 50 | // Create a user |
53 | await createUser(server.url, server.accessToken, 'toto', 'coucou') | 51 | await createUser({ url: server.url, accessToken: server.accessToken, username: 'toto', password: 'coucou' }) |
54 | 52 | ||
55 | // Create channel | 53 | // Create channel |
56 | const videoChannel = { | 54 | const videoChannel = { |
@@ -72,7 +70,7 @@ describe('Test update host scripts', function () { | |||
72 | 70 | ||
73 | killallServers([ server ]) | 71 | killallServers([ server ]) |
74 | // Run server with standard configuration | 72 | // Run server with standard configuration |
75 | server = await runServer(2) | 73 | await reRunServer(server) |
76 | 74 | ||
77 | const env = getEnvCli(server) | 75 | const env = getEnvCli(server) |
78 | await execCLI(`${env} npm run update-host`) | 76 | await execCLI(`${env} npm run update-host`) |
@@ -86,6 +84,13 @@ describe('Test update host scripts', function () { | |||
86 | const { body } = await makeActivityPubGetRequest(server.url, '/videos/watch/' + video.uuid) | 84 | const { body } = await makeActivityPubGetRequest(server.url, '/videos/watch/' + video.uuid) |
87 | 85 | ||
88 | expect(body.id).to.equal('http://localhost:9002/videos/watch/' + video.uuid) | 86 | expect(body.id).to.equal('http://localhost:9002/videos/watch/' + video.uuid) |
87 | |||
88 | const res = await getVideo(server.url, video.uuid) | ||
89 | const videoDetails: VideoDetails = res.body | ||
90 | |||
91 | expect(videoDetails.trackerUrls[0]).to.include(server.host) | ||
92 | expect(videoDetails.streamingPlaylists[0].playlistUrl).to.include(server.host) | ||
93 | expect(videoDetails.streamingPlaylists[0].segmentsSha256Url).to.include(server.host) | ||
89 | } | 94 | } |
90 | }) | 95 | }) |
91 | 96 | ||
@@ -100,7 +105,7 @@ describe('Test update host scripts', function () { | |||
100 | } | 105 | } |
101 | }) | 106 | }) |
102 | 107 | ||
103 | it('Should have update accounts url', async function () { | 108 | it('Should have updated accounts url', async function () { |
104 | const res = await getAccountsList(server.url) | 109 | const res = await getAccountsList(server.url) |
105 | expect(res.body.total).to.equal(3) | 110 | expect(res.body.total).to.equal(3) |
106 | 111 | ||
@@ -112,7 +117,7 @@ describe('Test update host scripts', function () { | |||
112 | } | 117 | } |
113 | }) | 118 | }) |
114 | 119 | ||
115 | it('Should update torrent hosts', async function () { | 120 | it('Should have updated torrent hosts', async function () { |
116 | this.timeout(30000) | 121 | this.timeout(30000) |
117 | 122 | ||
118 | const res = await getVideosList(server.url) | 123 | const res = await getVideosList(server.url) |
@@ -142,6 +147,6 @@ describe('Test update host scripts', function () { | |||
142 | }) | 147 | }) |
143 | 148 | ||
144 | after(async function () { | 149 | after(async function () { |
145 | killallServers([ server ]) | 150 | await cleanupTests([ server ]) |
146 | }) | 151 | }) |
147 | }) | 152 | }) |
diff --git a/server/tests/client.ts b/server/tests/client.ts index 06b4a9c5a..778dcd08e 100644 --- a/server/tests/client.ts +++ b/server/tests/client.ts | |||
@@ -4,18 +4,17 @@ import 'mocha' | |||
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import * as request from 'supertest' | 5 | import * as request from 'supertest' |
6 | import { | 6 | import { |
7 | flushTests, | 7 | cleanupTests, |
8 | flushAndRunServer, | ||
8 | getCustomConfig, | 9 | getCustomConfig, |
9 | getVideosList, | 10 | getVideosList, |
10 | killallServers, | ||
11 | makeHTMLRequest, | 11 | makeHTMLRequest, |
12 | runServer, | ||
13 | ServerInfo, | 12 | ServerInfo, |
14 | serverLogin, | 13 | serverLogin, |
15 | updateCustomConfig, | 14 | updateCustomConfig, |
16 | updateCustomSubConfig, | 15 | updateCustomSubConfig, |
17 | uploadVideo | 16 | uploadVideo |
18 | } from '../../shared/utils' | 17 | } from '../../shared/extra-utils' |
19 | 18 | ||
20 | const expect = chai.expect | 19 | const expect = chai.expect |
21 | 20 | ||
@@ -31,9 +30,7 @@ describe('Test a client controllers', function () { | |||
31 | before(async function () { | 30 | before(async function () { |
32 | this.timeout(120000) | 31 | this.timeout(120000) |
33 | 32 | ||
34 | await flushTests() | 33 | server = await flushAndRunServer(1) |
35 | |||
36 | server = await runServer(1) | ||
37 | server.accessToken = await serverLogin(server) | 34 | server.accessToken = await serverLogin(server) |
38 | 35 | ||
39 | const videoAttributes = { | 36 | const videoAttributes = { |
@@ -148,6 +145,6 @@ describe('Test a client controllers', function () { | |||
148 | }) | 145 | }) |
149 | 146 | ||
150 | after(async function () { | 147 | after(async function () { |
151 | killallServers([ server ]) | 148 | await cleanupTests([ server ]) |
152 | }) | 149 | }) |
153 | }) | 150 | }) |
diff --git a/server/tests/feeds/feeds.ts b/server/tests/feeds/feeds.ts index a771474bc..0dcdf09cf 100644 --- a/server/tests/feeds/feeds.ts +++ b/server/tests/feeds/feeds.ts | |||
@@ -3,6 +3,7 @@ | |||
3 | import * as chai from 'chai' | 3 | import * as chai from 'chai' |
4 | import 'mocha' | 4 | import 'mocha' |
5 | import { | 5 | import { |
6 | cleanupTests, | ||
6 | createUser, | 7 | createUser, |
7 | doubleFollow, | 8 | doubleFollow, |
8 | flushAndRunMultipleServers, | 9 | flushAndRunMultipleServers, |
@@ -13,10 +14,10 @@ import { | |||
13 | ServerInfo, | 14 | ServerInfo, |
14 | setAccessTokensToServers, | 15 | setAccessTokensToServers, |
15 | uploadVideo, userLogin | 16 | uploadVideo, userLogin |
16 | } from '../../../shared/utils' | 17 | } from '../../../shared/extra-utils' |
17 | import * as libxmljs from 'libxmljs' | 18 | import * as libxmljs from 'libxmljs' |
18 | import { addVideoCommentThread } from '../../../shared/utils/videos/video-comments' | 19 | import { addVideoCommentThread } from '../../../shared/extra-utils/videos/video-comments' |
19 | import { waitJobs } from '../../../shared/utils/server/jobs' | 20 | import { waitJobs } from '../../../shared/extra-utils/server/jobs' |
20 | import { User } from '../../../shared/models/users' | 21 | import { User } from '../../../shared/models/users' |
21 | 22 | ||
22 | chai.use(require('chai-xml')) | 23 | chai.use(require('chai-xml')) |
@@ -50,7 +51,7 @@ describe('Test syndication feeds', () => { | |||
50 | 51 | ||
51 | { | 52 | { |
52 | const attr = { username: 'john', password: 'password' } | 53 | const attr = { username: 'john', password: 'password' } |
53 | await createUser(servers[0].url, servers[0].accessToken, attr.username, attr.password) | 54 | await createUser({ url: servers[ 0 ].url, accessToken: servers[ 0 ].accessToken, username: attr.username, password: attr.password }) |
54 | userAccessToken = await userLogin(servers[0], attr) | 55 | userAccessToken = await userLogin(servers[0], attr) |
55 | 56 | ||
56 | const res = await getMyUserInformation(servers[0].url, userAccessToken) | 57 | const res = await getMyUserInformation(servers[0].url, userAccessToken) |
@@ -208,11 +209,6 @@ describe('Test syndication feeds', () => { | |||
208 | }) | 209 | }) |
209 | 210 | ||
210 | after(async function () { | 211 | after(async function () { |
211 | killallServers(servers) | 212 | await cleanupTests(servers) |
212 | |||
213 | // Keep the logs if the test failed | ||
214 | if (this['ok']) { | ||
215 | await flushTests() | ||
216 | } | ||
217 | }) | 213 | }) |
218 | }) | 214 | }) |
diff --git a/server/tests/fixtures/thumbnail-playlist.jpg b/server/tests/fixtures/thumbnail-playlist.jpg new file mode 100644 index 000000000..19db4f18c --- /dev/null +++ b/server/tests/fixtures/thumbnail-playlist.jpg | |||
Binary files differ | |||
diff --git a/server/tests/helpers/index.ts b/server/tests/helpers/index.ts index 551208245..03b971770 100644 --- a/server/tests/helpers/index.ts +++ b/server/tests/helpers/index.ts | |||
@@ -1,2 +1,3 @@ | |||
1 | import './core-utils' | 1 | import './core-utils' |
2 | import './comment-model' | 2 | import './comment-model' |
3 | import './request' | ||
diff --git a/server/tests/helpers/request.ts b/server/tests/helpers/request.ts new file mode 100644 index 000000000..a754bc6e2 --- /dev/null +++ b/server/tests/helpers/request.ts | |||
@@ -0,0 +1,48 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import 'mocha' | ||
4 | import { doRequest, doRequestAndSaveToFile } from '../../helpers/requests' | ||
5 | import { get4KFileUrl, root, wait } from '../../../shared/extra-utils' | ||
6 | import { join } from 'path' | ||
7 | import { pathExists, remove } from 'fs-extra' | ||
8 | import { expect } from 'chai' | ||
9 | |||
10 | describe('Request helpers', function () { | ||
11 | const destPath1 = join(root(), 'test-output-1.txt') | ||
12 | const destPath2 = join(root(), 'test-output-2.txt') | ||
13 | |||
14 | it('Should throw an error when the bytes limit is exceeded for request', async function () { | ||
15 | try { | ||
16 | await doRequest({ uri: get4KFileUrl() }, 3) | ||
17 | } catch { | ||
18 | return | ||
19 | } | ||
20 | |||
21 | throw new Error('No error thrown by do request') | ||
22 | }) | ||
23 | |||
24 | it('Should throw an error when the bytes limit is exceeded for request and save file', async function () { | ||
25 | try { | ||
26 | await doRequestAndSaveToFile({ uri: get4KFileUrl() }, destPath1, 3) | ||
27 | } catch { | ||
28 | |||
29 | await wait(500) | ||
30 | expect(await pathExists(destPath1)).to.be.false | ||
31 | return | ||
32 | } | ||
33 | |||
34 | throw new Error('No error thrown by do request and save to file') | ||
35 | }) | ||
36 | |||
37 | it('Should succeed if the file is below the limit', async function () { | ||
38 | await doRequest({ uri: get4KFileUrl() }, 5) | ||
39 | await doRequestAndSaveToFile({ uri: get4KFileUrl() }, destPath2, 5) | ||
40 | |||
41 | expect(await pathExists(destPath2)).to.be.true | ||
42 | }) | ||
43 | |||
44 | after(async function () { | ||
45 | await remove(destPath1) | ||
46 | await remove(destPath2) | ||
47 | }) | ||
48 | }) | ||
diff --git a/server/tests/misc-endpoints.ts b/server/tests/misc-endpoints.ts index 5f82719da..ed406e1bc 100644 --- a/server/tests/misc-endpoints.ts +++ b/server/tests/misc-endpoints.ts | |||
@@ -4,15 +4,14 @@ import 'mocha' | |||
4 | import * as chai from 'chai' | 4 | import * as chai from 'chai' |
5 | import { | 5 | import { |
6 | addVideoChannel, | 6 | addVideoChannel, |
7 | cleanupTests, | ||
7 | createUser, | 8 | createUser, |
8 | flushTests, | 9 | flushAndRunServer, |
9 | killallServers, | ||
10 | makeGetRequest, | 10 | makeGetRequest, |
11 | runServer, | ||
12 | ServerInfo, | 11 | ServerInfo, |
13 | setAccessTokensToServers, | 12 | setAccessTokensToServers, |
14 | uploadVideo | 13 | uploadVideo |
15 | } from '../../shared/utils' | 14 | } from '../../shared/extra-utils' |
16 | import { VideoPrivacy } from '../../shared/models/videos' | 15 | import { VideoPrivacy } from '../../shared/models/videos' |
17 | 16 | ||
18 | const expect = chai.expect | 17 | const expect = chai.expect |
@@ -23,9 +22,7 @@ describe('Test misc endpoints', function () { | |||
23 | before(async function () { | 22 | before(async function () { |
24 | this.timeout(120000) | 23 | this.timeout(120000) |
25 | 24 | ||
26 | await flushTests() | 25 | server = await flushAndRunServer(1) |
27 | |||
28 | server = await runServer(1) | ||
29 | await setAccessTokensToServers([ server ]) | 26 | await setAccessTokensToServers([ server ]) |
30 | }) | 27 | }) |
31 | 28 | ||
@@ -149,8 +146,8 @@ describe('Test misc endpoints', function () { | |||
149 | await addVideoChannel(server.url, server.accessToken, { name: 'channel1', displayName: 'channel 1' }) | 146 | await addVideoChannel(server.url, server.accessToken, { name: 'channel1', displayName: 'channel 1' }) |
150 | await addVideoChannel(server.url, server.accessToken, { name: 'channel2', displayName: 'channel 2' }) | 147 | await addVideoChannel(server.url, server.accessToken, { name: 'channel2', displayName: 'channel 2' }) |
151 | 148 | ||
152 | await createUser(server.url, server.accessToken, 'user1', 'password') | 149 | await createUser({ url: server.url, accessToken: server.accessToken, username: 'user1', password: 'password' }) |
153 | await createUser(server.url, server.accessToken, 'user2', 'password') | 150 | await createUser({ url: server.url, accessToken: server.accessToken, username: 'user2', password: 'password' }) |
154 | 151 | ||
155 | const res = await makeGetRequest({ | 152 | const res = await makeGetRequest({ |
156 | url: server.url, | 153 | url: server.url, |
@@ -174,6 +171,6 @@ describe('Test misc endpoints', function () { | |||
174 | }) | 171 | }) |
175 | 172 | ||
176 | after(async function () { | 173 | after(async function () { |
177 | killallServers([ server ]) | 174 | await cleanupTests([ server ]) |
178 | }) | 175 | }) |
179 | }) | 176 | }) |
diff --git a/server/tests/real-world/populate-database.ts b/server/tests/real-world/populate-database.ts index 016503498..b1c1688e7 100644 --- a/server/tests/real-world/populate-database.ts +++ b/server/tests/real-world/populate-database.ts | |||
@@ -6,11 +6,11 @@ import { | |||
6 | getVideosList, | 6 | getVideosList, |
7 | killallServers, | 7 | killallServers, |
8 | rateVideo, | 8 | rateVideo, |
9 | runServer, | 9 | flushAndRunServer, |
10 | ServerInfo, | 10 | ServerInfo, |
11 | setAccessTokensToServers, | 11 | setAccessTokensToServers, |
12 | uploadVideo | 12 | uploadVideo |
13 | } from '../../../shared/utils' | 13 | } from '../../../shared/extra-utils' |
14 | import * as Bluebird from 'bluebird' | 14 | import * as Bluebird from 'bluebird' |
15 | 15 | ||
16 | start() | 16 | start() |
@@ -19,11 +19,10 @@ start() | |||
19 | // ---------------------------------------------------------------------------- | 19 | // ---------------------------------------------------------------------------- |
20 | 20 | ||
21 | async function start () { | 21 | async function start () { |
22 | await flushTests() | ||
23 | 22 | ||
24 | console.log('Flushed tests.') | 23 | console.log('Flushed tests.') |
25 | 24 | ||
26 | const server = await runServer(6) | 25 | const server = await flushAndRunServer(6) |
27 | 26 | ||
28 | process.on('exit', async () => { | 27 | process.on('exit', async () => { |
29 | killallServers([ server ]) | 28 | killallServers([ server ]) |
@@ -78,7 +77,7 @@ function createUserCustom (server: ServerInfo) { | |||
78 | const username = Date.now().toString() + getRandomInt(0, 100000) | 77 | const username = Date.now().toString() + getRandomInt(0, 100000) |
79 | console.log('Creating user %s.', username) | 78 | console.log('Creating user %s.', username) |
80 | 79 | ||
81 | return createUser(server.url, server.accessToken, username, 'coucou') | 80 | return createUser({ url: server.url, accessToken: server.accessToken, username: username, password: 'coucou' }) |
82 | } | 81 | } |
83 | 82 | ||
84 | function uploadCustom (server: ServerInfo) { | 83 | function uploadCustom (server: ServerInfo) { |
diff --git a/server/tests/real-world/real-world.ts b/server/tests/real-world/real-world.ts index ac3baaf9a..8b070004d 100644 --- a/server/tests/real-world/real-world.ts +++ b/server/tests/real-world/real-world.ts | |||
@@ -16,8 +16,8 @@ import { | |||
16 | updateVideo, | 16 | updateVideo, |
17 | uploadVideo, viewVideo, | 17 | uploadVideo, viewVideo, |
18 | wait | 18 | wait |
19 | } from '../../../shared/utils' | 19 | } from '../../../shared/extra-utils' |
20 | import { getJobsListPaginationAndSort } from '../../../shared/utils/server/jobs' | 20 | import { getJobsListPaginationAndSort } from '../../../shared/extra-utils/server/jobs' |
21 | 21 | ||
22 | interface ServerInfo extends DefaultServerInfo { | 22 | interface ServerInfo extends DefaultServerInfo { |
23 | requestsNumber: number | 23 | requestsNumber: number |
diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 108c44218..59e9fcfc4 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts | |||
@@ -3,51 +3,83 @@ const netrc = require('netrc-parser').default | |||
3 | 3 | ||
4 | const version = require('../../../package.json').version | 4 | const version = require('../../../package.json').version |
5 | 5 | ||
6 | let settings = { | ||
7 | remotes: [], | ||
8 | default: 0 | ||
9 | } | ||
10 | |||
11 | interface Settings { | 6 | interface Settings { |
12 | remotes: any[], | 7 | remotes: any[], |
13 | default: number | 8 | default: number |
14 | } | 9 | } |
15 | 10 | ||
16 | async function getSettings () { | 11 | function getSettings () { |
17 | return new Promise<Settings>((res, rej) => { | 12 | return new Promise<Settings>((res, rej) => { |
18 | let settings = { | 13 | const defaultSettings = { |
19 | remotes: [], | 14 | remotes: [], |
20 | default: 0 | 15 | default: 0 |
21 | } as Settings | 16 | } |
17 | |||
22 | config.read((err, data) => { | 18 | config.read((err, data) => { |
23 | if (err) { | 19 | if (err) return rej(err) |
24 | return rej(err) | 20 | |
25 | } | 21 | return res(Object.keys(data).length === 0 ? defaultSettings : data) |
26 | return res(Object.keys(data).length === 0 ? settings : data) | ||
27 | }) | 22 | }) |
28 | }) | 23 | }) |
29 | } | 24 | } |
30 | 25 | ||
31 | async function writeSettings (settings) { | 26 | async function getNetrc () { |
27 | await netrc.load() | ||
28 | |||
29 | return netrc | ||
30 | } | ||
31 | |||
32 | function writeSettings (settings) { | ||
32 | return new Promise((res, rej) => { | 33 | return new Promise((res, rej) => { |
33 | config.write(settings, function (err) { | 34 | config.write(settings, function (err) { |
34 | if (err) { | 35 | if (err) return rej(err) |
35 | return rej(err) | 36 | |
36 | } | ||
37 | return res() | 37 | return res() |
38 | }) | 38 | }) |
39 | }) | 39 | }) |
40 | } | 40 | } |
41 | 41 | ||
42 | netrc.loadSync() | 42 | function getRemoteObjectOrDie (program: any, settings: Settings) { |
43 | if (!program['url'] || !program['username'] || !program['password']) { | ||
44 | // No remote and we don't have program parameters: throw | ||
45 | if (settings.remotes.length === 0) { | ||
46 | if (!program[ 'url' ]) console.error('--url field is required.') | ||
47 | if (!program[ 'username' ]) console.error('--username field is required.') | ||
48 | if (!program[ 'password' ]) console.error('--password field is required.') | ||
49 | |||
50 | return process.exit(-1) | ||
51 | } | ||
52 | |||
53 | let url: string = program['url'] | ||
54 | let username: string = program['username'] | ||
55 | let password: string = program['password'] | ||
56 | |||
57 | if (!url) { | ||
58 | url = settings.default !== -1 | ||
59 | ? settings.remotes[settings.default] | ||
60 | : settings.remotes[0] | ||
61 | } | ||
62 | |||
63 | if (!username) username = netrc.machines[url].login | ||
64 | if (!password) password = netrc.machines[url].password | ||
65 | |||
66 | return { url, username, password } | ||
67 | } | ||
68 | |||
69 | return { | ||
70 | url: program[ 'url' ], | ||
71 | username: program[ 'username' ], | ||
72 | password: program[ 'password' ] | ||
73 | } | ||
74 | } | ||
43 | 75 | ||
44 | // --------------------------------------------------------------------------- | 76 | // --------------------------------------------------------------------------- |
45 | 77 | ||
46 | export { | 78 | export { |
47 | version, | 79 | version, |
48 | config, | 80 | config, |
49 | settings, | ||
50 | getSettings, | 81 | getSettings, |
51 | writeSettings, | 82 | getNetrc, |
52 | netrc | 83 | getRemoteObjectOrDie, |
84 | writeSettings | ||
53 | } | 85 | } |
diff --git a/server/tools/peertube-auth.ts b/server/tools/peertube-auth.ts index a962944a4..8bc3d332c 100644 --- a/server/tools/peertube-auth.ts +++ b/server/tools/peertube-auth.ts | |||
@@ -1,22 +1,25 @@ | |||
1 | import * as program from 'commander' | 1 | import * as program from 'commander' |
2 | import * as prompt from 'prompt' | 2 | import * as prompt from 'prompt' |
3 | const Table = require('cli-table') | 3 | import { getSettings, writeSettings, getNetrc } from './cli' |
4 | import { getSettings, writeSettings, netrc } from './cli' | ||
5 | import { isHostValid } from '../helpers/custom-validators/servers' | 4 | import { isHostValid } from '../helpers/custom-validators/servers' |
6 | import { isUserUsernameValid } from '../helpers/custom-validators/users' | 5 | import { isUserUsernameValid } from '../helpers/custom-validators/users' |
7 | 6 | ||
7 | const Table = require('cli-table') | ||
8 | |||
8 | async function delInstance (url: string) { | 9 | async function delInstance (url: string) { |
9 | const settings = await getSettings() | 10 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) |
10 | 11 | ||
11 | settings.remotes.splice(settings.remotes.indexOf(url)) | 12 | settings.remotes.splice(settings.remotes.indexOf(url)) |
12 | await writeSettings(settings) | 13 | await writeSettings(settings) |
13 | 14 | ||
14 | delete netrc.machines[url] | 15 | delete netrc.machines[url] |
16 | |||
15 | await netrc.save() | 17 | await netrc.save() |
16 | } | 18 | } |
17 | 19 | ||
18 | async function setInstance (url: string, username: string, password: string) { | 20 | async function setInstance (url: string, username: string, password: string) { |
19 | const settings = await getSettings() | 21 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) |
22 | |||
20 | if (settings.remotes.indexOf(url) === -1) { | 23 | if (settings.remotes.indexOf(url) === -1) { |
21 | settings.remotes.push(url) | 24 | settings.remotes.push(url) |
22 | } | 25 | } |
@@ -82,12 +85,13 @@ program | |||
82 | .command('list') | 85 | .command('list') |
83 | .description('lists registered remote instances') | 86 | .description('lists registered remote instances') |
84 | .action(async () => { | 87 | .action(async () => { |
85 | const settings = await getSettings() | 88 | const [ settings, netrc ] = await Promise.all([ getSettings(), getNetrc() ]) |
89 | |||
86 | const table = new Table({ | 90 | const table = new Table({ |
87 | head: ['instance', 'login'], | 91 | head: ['instance', 'login'], |
88 | colWidths: [30, 30] | 92 | colWidths: [30, 30] |
89 | }) | 93 | }) |
90 | netrc.loadSync() | 94 | |
91 | settings.remotes.forEach(element => { | 95 | settings.remotes.forEach(element => { |
92 | table.push([ | 96 | table.push([ |
93 | element, | 97 | element, |
diff --git a/server/tools/peertube-get-access-token.ts b/server/tools/peertube-get-access-token.ts index a68665f5b..85660de2c 100644 --- a/server/tools/peertube-get-access-token.ts +++ b/server/tools/peertube-get-access-token.ts | |||
@@ -6,7 +6,7 @@ import { | |||
6 | Server, | 6 | Server, |
7 | Client, | 7 | Client, |
8 | User | 8 | User |
9 | } from '../../shared/utils' | 9 | } from '../../shared/extra-utils' |
10 | 10 | ||
11 | program | 11 | program |
12 | .option('-u, --url <url>', 'Server url') | 12 | .option('-u, --url <url>', 'Server url') |
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index f50aafc35..9a366dbbd 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts | |||
@@ -5,14 +5,14 @@ import * as program from 'commander' | |||
5 | import { join } from 'path' | 5 | import { join } from 'path' |
6 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../shared/models/videos' |
7 | import { doRequestAndSaveToFile } from '../helpers/requests' | 7 | import { doRequestAndSaveToFile } from '../helpers/requests' |
8 | import { CONSTRAINTS_FIELDS } from '../initializers' | 8 | import { CONSTRAINTS_FIELDS } from '../initializers/constants' |
9 | import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/utils/index' | 9 | import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index' |
10 | import { truncate } from 'lodash' | 10 | import { truncate } from 'lodash' |
11 | import * as prompt from 'prompt' | 11 | import * as prompt from 'prompt' |
12 | import { remove } from 'fs-extra' | 12 | import { remove } from 'fs-extra' |
13 | import { sha256 } from '../helpers/core-utils' | 13 | import { sha256 } from '../helpers/core-utils' |
14 | import { safeGetYoutubeDL } from '../helpers/youtube-dl' | 14 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' |
15 | import { getSettings, netrc } from './cli' | 15 | import { getNetrc, getRemoteObjectOrDie, getSettings } from './cli' |
16 | 16 | ||
17 | let accessToken: string | 17 | let accessToken: string |
18 | let client: { id: string, secret: string } | 18 | let client: { id: string, secret: string } |
@@ -32,58 +32,30 @@ program | |||
32 | .option('-v, --verbose', 'Verbose mode') | 32 | .option('-v, --verbose', 'Verbose mode') |
33 | .parse(process.argv) | 33 | .parse(process.argv) |
34 | 34 | ||
35 | getSettings() | 35 | Promise.all([ getSettings(), getNetrc() ]) |
36 | .then(settings => { | 36 | .then(([ settings, netrc ]) => { |
37 | if ( | 37 | const { url, username, password } = getRemoteObjectOrDie(program, settings) |
38 | (!program['url'] || | ||
39 | !program['username'] || | ||
40 | !program['password']) && | ||
41 | (settings.remotes.length === 0) | ||
42 | ) { | ||
43 | if (!program['url']) console.error('--url field is required.') | ||
44 | if (!program['username']) console.error('--username field is required.') | ||
45 | if (!program['password']) console.error('--password field is required.') | ||
46 | if (!program['targetUrl']) console.error('--targetUrl field is required.') | ||
47 | process.exit(-1) | ||
48 | } | ||
49 | |||
50 | if ( | ||
51 | (!program['url'] || | ||
52 | !program['username'] || | ||
53 | !program['password']) && | ||
54 | (settings.remotes.length > 0) | ||
55 | ) { | ||
56 | if (!program['url']) { | ||
57 | program['url'] = (settings.default !== -1) ? | ||
58 | settings.remotes[settings.default] : | ||
59 | settings.remotes[0] | ||
60 | } | ||
61 | 38 | ||
62 | if (!program['username']) program['username'] = netrc.machines[program['url']].login | 39 | if (!program[ 'targetUrl' ]) { |
63 | if (!program['password']) program['password'] = netrc.machines[program['url']].password | 40 | console.error('--targetUrl field is required.') |
64 | } | ||
65 | 41 | ||
66 | if ( | 42 | process.exit(-1) |
67 | !program['targetUrl'] | 43 | } |
68 | ) { | ||
69 | if (!program['targetUrl']) console.error('--targetUrl field is required.') | ||
70 | process.exit(-1) | ||
71 | } | ||
72 | 44 | ||
73 | removeEndSlashes(program['url']) | 45 | removeEndSlashes(url) |
74 | removeEndSlashes(program['targetUrl']) | 46 | removeEndSlashes(program[ 'targetUrl' ]) |
75 | 47 | ||
76 | const user = { | 48 | const user = { |
77 | username: program['username'], | 49 | username: username, |
78 | password: program['password'] | 50 | password: password |
79 | } | 51 | } |
80 | 52 | ||
81 | run(user, program['url']) | 53 | run(user, url) |
82 | .catch(err => { | 54 | .catch(err => { |
83 | console.error(err) | 55 | console.error(err) |
84 | process.exit(-1) | 56 | process.exit(-1) |
85 | }) | 57 | }) |
86 | }) | 58 | }) |
87 | 59 | ||
88 | async function promptPassword () { | 60 | async function promptPassword () { |
89 | return new Promise((res, rej) => { | 61 | return new Promise((res, rej) => { |
@@ -126,7 +98,7 @@ async function run (user, url: string) { | |||
126 | const youtubeDL = await safeGetYoutubeDL() | 98 | const youtubeDL = await safeGetYoutubeDL() |
127 | 99 | ||
128 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] | 100 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] |
129 | youtubeDL.getInfo(program['targetUrl'], options, processOptions, async (err, info) => { | 101 | youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { |
130 | if (err) { | 102 | if (err) { |
131 | console.log(err.message) | 103 | console.log(err.message) |
132 | process.exit(1) | 104 | process.exit(1) |
@@ -143,20 +115,20 @@ async function run (user, url: string) { | |||
143 | console.log('Will download and upload %d videos.\n', infoArray.length) | 115 | console.log('Will download and upload %d videos.\n', infoArray.length) |
144 | 116 | ||
145 | for (const info of infoArray) { | 117 | for (const info of infoArray) { |
146 | await processVideo(info, program['language'], processOptions.cwd, url, user) | 118 | await processVideo(info, program[ 'language' ], processOptions.cwd, url, user) |
147 | } | 119 | } |
148 | 120 | ||
149 | console.log('Video/s for user %s imported: %s', program['username'], program['targetUrl']) | 121 | console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ]) |
150 | process.exit(0) | 122 | process.exit(0) |
151 | }) | 123 | }) |
152 | } | 124 | } |
153 | 125 | ||
154 | function processVideo (info: any, languageCode: string, cwd: string, url: string, user) { | 126 | function processVideo (info: any, languageCode: string, cwd: string, url: string, user) { |
155 | return new Promise(async res => { | 127 | return new Promise(async res => { |
156 | if (program['verbose']) console.log('Fetching object.', info) | 128 | if (program[ 'verbose' ]) console.log('Fetching object.', info) |
157 | 129 | ||
158 | const videoInfo = await fetchObject(info) | 130 | const videoInfo = await fetchObject(info) |
159 | if (program['verbose']) console.log('Fetched object.', videoInfo) | 131 | if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo) |
160 | 132 | ||
161 | const result = await searchVideoWithSort(url, videoInfo.title, '-match') | 133 | const result = await searchVideoWithSort(url, videoInfo.title, '-match') |
162 | 134 | ||
@@ -197,9 +169,9 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st | |||
197 | let tags = [] | 169 | let tags = [] |
198 | if (Array.isArray(videoInfo.tags)) { | 170 | if (Array.isArray(videoInfo.tags)) { |
199 | tags = videoInfo.tags | 171 | tags = videoInfo.tags |
200 | .filter(t => t.length < CONSTRAINTS_FIELDS.VIDEOS.TAG.max && t.length > CONSTRAINTS_FIELDS.VIDEOS.TAG.min) | 172 | .filter(t => t.length < CONSTRAINTS_FIELDS.VIDEOS.TAG.max && t.length > CONSTRAINTS_FIELDS.VIDEOS.TAG.min) |
201 | .map(t => t.normalize()) | 173 | .map(t => t.normalize()) |
202 | .slice(0, 5) | 174 | .slice(0, 5) |
203 | } | 175 | } |
204 | 176 | ||
205 | let thumbnailfile | 177 | let thumbnailfile |
@@ -212,6 +184,8 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st | |||
212 | }, thumbnailfile) | 184 | }, thumbnailfile) |
213 | } | 185 | } |
214 | 186 | ||
187 | const originallyPublishedAt = buildOriginallyPublishedAt(videoInfo) | ||
188 | |||
215 | const videoAttributes = { | 189 | const videoAttributes = { |
216 | name: truncate(videoInfo.title, { | 190 | name: truncate(videoInfo.title, { |
217 | 'length': CONSTRAINTS_FIELDS.VIDEOS.NAME.max, | 191 | 'length': CONSTRAINTS_FIELDS.VIDEOS.NAME.max, |
@@ -224,13 +198,15 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st | |||
224 | nsfw: isNSFW(videoInfo), | 198 | nsfw: isNSFW(videoInfo), |
225 | waitTranscoding: true, | 199 | waitTranscoding: true, |
226 | commentsEnabled: true, | 200 | commentsEnabled: true, |
201 | downloadEnabled: true, | ||
227 | description: videoInfo.description || undefined, | 202 | description: videoInfo.description || undefined, |
228 | support: undefined, | 203 | support: undefined, |
229 | tags, | 204 | tags, |
230 | privacy: VideoPrivacy.PUBLIC, | 205 | privacy: VideoPrivacy.PUBLIC, |
231 | fixture: videoPath, | 206 | fixture: videoPath, |
232 | thumbnailfile, | 207 | thumbnailfile, |
233 | previewfile: thumbnailfile | 208 | previewfile: thumbnailfile, |
209 | originallyPublishedAt: originallyPublishedAt ? originallyPublishedAt.toISOString() : null | ||
234 | } | 210 | } |
235 | 211 | ||
236 | console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) | 212 | console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) |
@@ -259,7 +235,7 @@ async function uploadVideoOnPeerTube (videoInfo: any, videoPath: string, cwd: st | |||
259 | async function getCategory (categories: string[], url: string) { | 235 | async function getCategory (categories: string[], url: string) { |
260 | if (!categories) return undefined | 236 | if (!categories) return undefined |
261 | 237 | ||
262 | const categoryString = categories[0] | 238 | const categoryString = categories[ 0 ] |
263 | 239 | ||
264 | if (categoryString === 'News & Politics') return 11 | 240 | if (categoryString === 'News & Politics') return 11 |
265 | 241 | ||
@@ -267,7 +243,7 @@ async function getCategory (categories: string[], url: string) { | |||
267 | const categoriesServer = res.body | 243 | const categoriesServer = res.body |
268 | 244 | ||
269 | for (const key of Object.keys(categoriesServer)) { | 245 | for (const key of Object.keys(categoriesServer)) { |
270 | const categoryServer = categoriesServer[key] | 246 | const categoryServer = categoriesServer[ key ] |
271 | if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10) | 247 | if (categoryString.toLowerCase() === categoryServer.toLowerCase()) return parseInt(key, 10) |
272 | } | 248 | } |
273 | 249 | ||
@@ -291,12 +267,12 @@ function normalizeObject (obj: any) { | |||
291 | // Deprecated key | 267 | // Deprecated key |
292 | if (key === 'resolution') continue | 268 | if (key === 'resolution') continue |
293 | 269 | ||
294 | const value = obj[key] | 270 | const value = obj[ key ] |
295 | 271 | ||
296 | if (typeof value === 'string') { | 272 | if (typeof value === 'string') { |
297 | newObj[key] = value.normalize() | 273 | newObj[ key ] = value.normalize() |
298 | } else { | 274 | } else { |
299 | newObj[key] = value | 275 | newObj[ key ] = value |
300 | } | 276 | } |
301 | } | 277 | } |
302 | 278 | ||
diff --git a/server/tools/peertube-upload.ts b/server/tools/peertube-upload.ts index cc7bd9b4c..687f2e60b 100644 --- a/server/tools/peertube-upload.ts +++ b/server/tools/peertube-upload.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import * as program from 'commander' | 1 | import * as program from 'commander' |
2 | import { access, constants } from 'fs-extra' | 2 | import { access, constants } from 'fs-extra' |
3 | import { isAbsolute } from 'path' | 3 | import { isAbsolute } from 'path' |
4 | import { getClient, login } from '../../shared/utils' | 4 | import { getClient, login } from '../../shared/extra-utils' |
5 | import { uploadVideo } from '../../shared/utils/' | 5 | import { uploadVideo } from '../../shared/extra-utils/' |
6 | import { VideoPrivacy } from '../../shared/models/videos' | 6 | import { VideoPrivacy } from '../../shared/models/videos' |
7 | import { netrc, getSettings } from './cli' | 7 | import { getRemoteObjectOrDie, getSettings } from './cli' |
8 | 8 | ||
9 | program | 9 | program |
10 | .name('upload') | 10 | .name('upload') |
@@ -26,48 +26,15 @@ program | |||
26 | .option('-f, --file <file>', 'Video absolute file path') | 26 | .option('-f, --file <file>', 'Video absolute file path') |
27 | .parse(process.argv) | 27 | .parse(process.argv) |
28 | 28 | ||
29 | if (!program['tags']) program['tags'] = [] | ||
30 | if (!program['nsfw']) program['nsfw'] = false | ||
31 | if (!program['privacy']) program['privacy'] = VideoPrivacy.PUBLIC | ||
32 | if (!program['commentsEnabled']) program['commentsEnabled'] = false | ||
33 | |||
34 | getSettings() | 29 | getSettings() |
35 | .then(settings => { | 30 | .then(settings => { |
36 | if ( | 31 | const { url, username, password } = getRemoteObjectOrDie(program, settings) |
37 | (!program['url'] || | ||
38 | !program['username'] || | ||
39 | !program['password']) && | ||
40 | (settings.remotes.length === 0) | ||
41 | ) { | ||
42 | if (!program['url']) console.error('--url field is required.') | ||
43 | if (!program['username']) console.error('--username field is required.') | ||
44 | if (!program['password']) console.error('--password field is required.') | ||
45 | if (!program['videoName']) console.error('--video-name field is required.') | ||
46 | if (!program['file']) console.error('--file field is required.') | ||
47 | process.exit(-1) | ||
48 | } | ||
49 | 32 | ||
50 | if ( | 33 | if (!program['videoName'] || !program['file'] || !program['channelId']) { |
51 | (!program['url'] || | 34 | if (!program['videoName']) console.error('--video-name is required.') |
52 | !program['username'] || | 35 | if (!program['file']) console.error('--file is required.') |
53 | !program['password']) && | 36 | if (!program['channelId']) console.error('--channel-id is required.') |
54 | (settings.remotes.length > 0) | ||
55 | ) { | ||
56 | if (!program['url']) { | ||
57 | program['url'] = (settings.default !== -1) ? | ||
58 | settings.remotes[settings.default] : | ||
59 | settings.remotes[0] | ||
60 | } | ||
61 | if (!program['username']) program['username'] = netrc.machines[program['url']].login | ||
62 | if (!program['password']) program['password'] = netrc.machines[program['url']].password | ||
63 | } | ||
64 | 37 | ||
65 | if ( | ||
66 | !program['videoName'] || | ||
67 | !program['file'] | ||
68 | ) { | ||
69 | if (!program['videoName']) console.error('--video-name field is required.') | ||
70 | if (!program['file']) console.error('--file field is required.') | ||
71 | process.exit(-1) | 38 | process.exit(-1) |
72 | } | 39 | } |
73 | 40 | ||
@@ -76,28 +43,25 @@ getSettings() | |||
76 | process.exit(-1) | 43 | process.exit(-1) |
77 | } | 44 | } |
78 | 45 | ||
79 | run().catch(err => { | 46 | run(url, username, password).catch(err => { |
80 | console.error(err) | 47 | console.error(err) |
81 | process.exit(-1) | 48 | process.exit(-1) |
82 | }) | 49 | }) |
83 | }) | 50 | }) |
84 | 51 | ||
85 | async function run () { | 52 | async function run (url: string, username: string, password: string) { |
86 | const res = await getClient(program[ 'url' ]) | 53 | const resClient = await getClient(program[ 'url' ]) |
87 | const client = { | 54 | const client = { |
88 | id: res.body.client_id, | 55 | id: resClient.body.client_id, |
89 | secret: res.body.client_secret | 56 | secret: resClient.body.client_secret |
90 | } | 57 | } |
91 | 58 | ||
92 | const user = { | 59 | const user = { username, password } |
93 | username: program[ 'username' ], | ||
94 | password: program[ 'password' ] | ||
95 | } | ||
96 | 60 | ||
97 | let accessToken: string | 61 | let accessToken: string |
98 | try { | 62 | try { |
99 | const res2 = await login(program[ 'url' ], client, user) | 63 | const res = await login(url, client, user) |
100 | accessToken = res2.body.access_token | 64 | accessToken = res.body.access_token |
101 | } catch (err) { | 65 | } catch (err) { |
102 | throw new Error('Cannot authenticate. Please check your username/password.') | 66 | throw new Error('Cannot authenticate. Please check your username/password.') |
103 | } | 67 | } |
@@ -108,26 +72,32 @@ async function run () { | |||
108 | 72 | ||
109 | const videoAttributes = { | 73 | const videoAttributes = { |
110 | name: program['videoName'], | 74 | name: program['videoName'], |
111 | category: program['category'], | 75 | category: program['category'] || undefined, |
112 | channelId: program['channelId'], | 76 | channelId: program['channelId'], |
113 | licence: program['licence'], | 77 | licence: program['licence'] || undefined, |
114 | language: program['language'], | 78 | language: program['language'] || undefined, |
115 | nsfw: program['nsfw'], | 79 | nsfw: program['nsfw'] !== undefined ? program['nsfw'] : false, |
116 | description: program['videoDescription'], | 80 | description: program['videoDescription'] || '', |
117 | tags: program['tags'], | 81 | tags: program['tags'] || [], |
118 | commentsEnabled: program['commentsEnabled'], | 82 | commentsEnabled: program['commentsEnabled'] !== undefined ? program['commentsEnabled'] : true, |
83 | downloadEnabled: program['downloadEnabled'] !== undefined ? program['downloadEnabled'] : true, | ||
119 | fixture: program['file'], | 84 | fixture: program['file'], |
120 | thumbnailfile: program['thumbnail'], | 85 | thumbnailfile: program['thumbnail'], |
121 | previewfile: program['preview'], | 86 | previewfile: program['preview'], |
122 | waitTranscoding: true, | 87 | waitTranscoding: true, |
123 | privacy: program['privacy'], | 88 | privacy: program['privacy'] || VideoPrivacy.PUBLIC, |
124 | support: undefined | 89 | support: undefined |
125 | } | 90 | } |
126 | 91 | ||
127 | await uploadVideo(program[ 'url' ], accessToken, videoAttributes) | 92 | try { |
128 | 93 | await uploadVideo(url, accessToken, videoAttributes) | |
129 | console.log(`Video ${program['videoName']} uploaded.`) | 94 | console.log(`Video ${program['videoName']} uploaded.`) |
130 | process.exit(0) | 95 | process.exit(0) |
96 | } catch (err) { | ||
97 | console.log('coucou') | ||
98 | console.error(require('util').inspect(err)) | ||
99 | process.exit(-1) | ||
100 | } | ||
131 | } | 101 | } |
132 | 102 | ||
133 | // ---------------------------------------------------------------------------- | 103 | // ---------------------------------------------------------------------------- |
diff --git a/server/typings/express.ts b/server/typings/express.ts new file mode 100644 index 000000000..324d78662 --- /dev/null +++ b/server/typings/express.ts | |||
@@ -0,0 +1,82 @@ | |||
1 | import { VideoChannelModel } from '../models/video/video-channel' | ||
2 | import { VideoPlaylistModel } from '../models/video/video-playlist' | ||
3 | import { VideoPlaylistElementModel } from '../models/video/video-playlist-element' | ||
4 | import { UserModel } from '../models/account/user' | ||
5 | import { VideoModel } from '../models/video/video' | ||
6 | import { AccountModel } from '../models/account/account' | ||
7 | import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership' | ||
8 | import { ActorModel } from '../models/activitypub/actor' | ||
9 | import { VideoCommentModel } from '../models/video/video-comment' | ||
10 | import { VideoShareModel } from '../models/video/video-share' | ||
11 | import { AccountVideoRateModel } from '../models/account/account-video-rate' | ||
12 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | ||
13 | import { ServerModel } from '../models/server/server' | ||
14 | import { VideoFileModel } from '../models/video/video-file' | ||
15 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
16 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | ||
17 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | ||
18 | import { VideoImportModel } from '../models/video/video-import' | ||
19 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
20 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
21 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
22 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
23 | |||
24 | declare module 'express' { | ||
25 | |||
26 | interface Response { | ||
27 | locals: { | ||
28 | video?: VideoModel | ||
29 | videoShare?: VideoShareModel | ||
30 | videoFile?: VideoFileModel | ||
31 | |||
32 | videoImport?: VideoImportModel | ||
33 | |||
34 | videoBlacklist?: VideoBlacklistModel | ||
35 | |||
36 | videoCaption?: VideoCaptionModel | ||
37 | |||
38 | videoAbuse?: VideoAbuseModel | ||
39 | |||
40 | videoStreamingPlaylist?: VideoStreamingPlaylistModel | ||
41 | |||
42 | videoChannel?: VideoChannelModel | ||
43 | |||
44 | videoPlaylist?: VideoPlaylistModel | ||
45 | videoPlaylistElement?: VideoPlaylistElementModel | ||
46 | |||
47 | accountVideoRate?: AccountVideoRateModel | ||
48 | |||
49 | videoComment?: VideoCommentModel | ||
50 | videoCommentThread?: VideoCommentModel | ||
51 | |||
52 | follow?: ActorFollowModel | ||
53 | subscription?: ActorFollowModel | ||
54 | |||
55 | nextOwner?: AccountModel | ||
56 | videoChangeOwnership?: VideoChangeOwnershipModel | ||
57 | account?: AccountModel | ||
58 | actor?: ActorModel | ||
59 | user?: UserModel | ||
60 | |||
61 | server?: ServerModel | ||
62 | |||
63 | videoRedundancy?: VideoRedundancyModel | ||
64 | |||
65 | accountBlock?: AccountBlocklistModel | ||
66 | serverBlock?: ServerBlocklistModel | ||
67 | |||
68 | oauth?: { | ||
69 | token: { | ||
70 | User: UserModel | ||
71 | user: UserModel | ||
72 | } | ||
73 | } | ||
74 | |||
75 | signature?: { | ||
76 | actor: ActorModel | ||
77 | } | ||
78 | |||
79 | authenticated?: boolean | ||
80 | } | ||
81 | } | ||
82 | } | ||
diff --git a/server/typings/sequelize.ts b/server/typings/sequelize.ts new file mode 100644 index 000000000..9cd83612d --- /dev/null +++ b/server/typings/sequelize.ts | |||
@@ -0,0 +1,18 @@ | |||
1 | import { Model } from 'sequelize-typescript' | ||
2 | |||
3 | // Thanks to sequelize-typescript: https://github.com/RobinBuschmann/sequelize-typescript | ||
4 | |||
5 | export type Diff<T extends string | symbol | number, U extends string | symbol | number> = | ||
6 | ({ [P in T]: P } & { [P in U]: never } & { [ x: string ]: never })[T] | ||
7 | |||
8 | export type Omit<T, K extends keyof T> = { [P in Diff<keyof T, K>]: T[P] } | ||
9 | |||
10 | export type RecursivePartial<T> = { [P in keyof T]?: RecursivePartial<T[P]> } | ||
11 | |||
12 | export type FilteredModelAttributes<T extends Model<T>> = RecursivePartial<Omit<T, keyof Model<any>>> & { | ||
13 | id?: number | any | ||
14 | createdAt?: Date | any | ||
15 | updatedAt?: Date | any | ||
16 | deletedAt?: Date | any | ||
17 | version?: number | any | ||
18 | } | ||