diff options
Diffstat (limited to 'server')
-rw-r--r-- | server/controllers/api/remote/videos.ts | 30 | ||||
-rw-r--r-- | server/controllers/api/videos/index.ts | 11 | ||||
-rw-r--r-- | server/middlewares/validators/videos.ts | 16 | ||||
-rw-r--r-- | server/tests/real-world/real-world.ts | 102 |
4 files changed, 109 insertions, 50 deletions
diff --git a/server/controllers/api/remote/videos.ts b/server/controllers/api/remote/videos.ts index 9dd4afdb5..23023211f 100644 --- a/server/controllers/api/remote/videos.ts +++ b/server/controllers/api/remote/videos.ts | |||
@@ -17,7 +17,7 @@ import { | |||
17 | } from '../../../middlewares' | 17 | } from '../../../middlewares' |
18 | import { logger, retryTransactionWrapper } from '../../../helpers' | 18 | import { logger, retryTransactionWrapper } from '../../../helpers' |
19 | import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' | 19 | import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' |
20 | import { PodInstance } from '../../../models' | 20 | import { PodInstance, VideoFileInstance } from '../../../models' |
21 | import { | 21 | import { |
22 | RemoteVideoRequest, | 22 | RemoteVideoRequest, |
23 | RemoteVideoCreateData, | 23 | RemoteVideoCreateData, |
@@ -81,7 +81,7 @@ function remoteVideos (req: express.Request, res: express.Response, next: expres | |||
81 | // Get the function we need to call in order to process the request | 81 | // Get the function we need to call in order to process the request |
82 | const fun = functionsHash[request.type] | 82 | const fun = functionsHash[request.type] |
83 | if (fun === undefined) { | 83 | if (fun === undefined) { |
84 | logger.error('Unkown remote request type %s.', request.type) | 84 | logger.error('Unknown remote request type %s.', request.type) |
85 | return | 85 | return |
86 | } | 86 | } |
87 | 87 | ||
@@ -176,7 +176,7 @@ function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInsta | |||
176 | return quickAndDirtyUpdatesVideoToFriends(qadusParams, t) | 176 | return quickAndDirtyUpdatesVideoToFriends(qadusParams, t) |
177 | }) | 177 | }) |
178 | }) | 178 | }) |
179 | .then(() => logger.info('Remote video event processed for video %s.', eventData.uuid)) | 179 | .then(() => logger.info('Remote video event processed for video with uuid %s.', eventData.uuid)) |
180 | .catch(err => { | 180 | .catch(err => { |
181 | logger.debug('Cannot process a video event.', err) | 181 | logger.debug('Cannot process a video event.', err) |
182 | throw err | 182 | throw err |
@@ -193,14 +193,14 @@ function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, f | |||
193 | } | 193 | } |
194 | 194 | ||
195 | function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) { | 195 | function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) { |
196 | let videoName | 196 | let videoUUID = '' |
197 | 197 | ||
198 | return db.sequelize.transaction(t => { | 198 | return db.sequelize.transaction(t => { |
199 | return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid) | 199 | return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid) |
200 | .then(videoInstance => { | 200 | .then(videoInstance => { |
201 | const options = { transaction: t } | 201 | const options = { transaction: t } |
202 | 202 | ||
203 | videoName = videoInstance.name | 203 | videoUUID = videoInstance.uuid |
204 | 204 | ||
205 | if (videoData.views) { | 205 | if (videoData.views) { |
206 | videoInstance.set('views', videoData.views) | 206 | videoInstance.set('views', videoData.views) |
@@ -217,7 +217,7 @@ function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodI | |||
217 | return videoInstance.save(options) | 217 | return videoInstance.save(options) |
218 | }) | 218 | }) |
219 | }) | 219 | }) |
220 | .then(() => logger.info('Remote video %s quick and dirty updated', videoName)) | 220 | .then(() => logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)) |
221 | .catch(err => logger.debug('Cannot quick and dirty update the remote video.', err)) | 221 | .catch(err => logger.debug('Cannot quick and dirty update the remote video.', err)) |
222 | } | 222 | } |
223 | 223 | ||
@@ -315,7 +315,7 @@ function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodI | |||
315 | return videoCreated.setTags(tagInstances, options) | 315 | return videoCreated.setTags(tagInstances, options) |
316 | }) | 316 | }) |
317 | }) | 317 | }) |
318 | .then(() => logger.info('Remote video %s inserted.', videoToCreateData.name)) | 318 | .then(() => logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)) |
319 | .catch(err => { | 319 | .catch(err => { |
320 | logger.debug('Cannot insert the remote video.', err) | 320 | logger.debug('Cannot insert the remote video.', err) |
321 | throw err | 321 | throw err |
@@ -361,7 +361,17 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from | |||
361 | return videoInstance.save(options).then(() => ({ videoInstance, tagInstances })) | 361 | return videoInstance.save(options).then(() => ({ videoInstance, tagInstances })) |
362 | }) | 362 | }) |
363 | .then(({ tagInstances, videoInstance }) => { | 363 | .then(({ tagInstances, videoInstance }) => { |
364 | const tasks = [] | 364 | const tasks: Promise<void>[] = [] |
365 | |||
366 | // Remove old video files | ||
367 | videoInstance.VideoFiles.forEach(videoFile => { | ||
368 | tasks.push(videoFile.destroy()) | ||
369 | }) | ||
370 | |||
371 | return Promise.all(tasks).then(() => ({ tagInstances, videoInstance })) | ||
372 | }) | ||
373 | .then(({ tagInstances, videoInstance }) => { | ||
374 | const tasks: Promise<VideoFileInstance>[] = [] | ||
365 | const options = { | 375 | const options = { |
366 | transaction: t | 376 | transaction: t |
367 | } | 377 | } |
@@ -386,7 +396,7 @@ function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, from | |||
386 | return videoInstance.setTags(tagInstances, options) | 396 | return videoInstance.setTags(tagInstances, options) |
387 | }) | 397 | }) |
388 | }) | 398 | }) |
389 | .then(() => logger.info('Remote video %s updated', videoAttributesToUpdate.name)) | 399 | .then(() => logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)) |
390 | .catch(err => { | 400 | .catch(err => { |
391 | // This is just a debug because we will retry the insert | 401 | // This is just a debug because we will retry the insert |
392 | logger.debug('Cannot update the remote video.', err) | 402 | logger.debug('Cannot update the remote video.', err) |
@@ -398,7 +408,7 @@ function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: P | |||
398 | // We need the instance because we have to remove some other stuffs (thumbnail etc) | 408 | // We need the instance because we have to remove some other stuffs (thumbnail etc) |
399 | return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid) | 409 | return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid) |
400 | .then(video => { | 410 | .then(video => { |
401 | logger.debug('Removing remote video %s.', video.uuid) | 411 | logger.debug('Removing remote video with uuid %s.', video.uuid) |
402 | return video.destroy() | 412 | return video.destroy() |
403 | }) | 413 | }) |
404 | .catch(err => { | 414 | .catch(err => { |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 3a19fe989..7a9cd9d37 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -157,6 +157,7 @@ function addVideoRetryWrapper (req: express.Request, res: express.Response, next | |||
157 | 157 | ||
158 | function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { | 158 | function addVideo (req: express.Request, res: express.Response, videoPhysicalFile: Express.Multer.File) { |
159 | const videoInfo: VideoCreate = req.body | 159 | const videoInfo: VideoCreate = req.body |
160 | let videoUUID = '' | ||
160 | 161 | ||
161 | return db.sequelize.transaction(t => { | 162 | return db.sequelize.transaction(t => { |
162 | const user = res.locals.oauth.token.User | 163 | const user = res.locals.oauth.token.User |
@@ -241,6 +242,7 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil | |||
241 | .then(videoCreated => { | 242 | .then(videoCreated => { |
242 | // Do not forget to add Author information to the created video | 243 | // Do not forget to add Author information to the created video |
243 | videoCreated.Author = author | 244 | videoCreated.Author = author |
245 | videoUUID = videoCreated.uuid | ||
244 | 246 | ||
245 | return { tagInstances, video: videoCreated, videoFile } | 247 | return { tagInstances, video: videoCreated, videoFile } |
246 | }) | 248 | }) |
@@ -274,7 +276,7 @@ function addVideo (req: express.Request, res: express.Response, videoPhysicalFil | |||
274 | }) | 276 | }) |
275 | }) | 277 | }) |
276 | }) | 278 | }) |
277 | .then(() => logger.info('Video with name %s created.', videoInfo.name)) | 279 | .then(() => logger.info('Video with name %s and uuid %s created.', videoInfo.name, videoUUID)) |
278 | .catch((err: Error) => { | 280 | .catch((err: Error) => { |
279 | logger.debug('Cannot insert the video.', err) | 281 | logger.debug('Cannot insert the video.', err) |
280 | throw err | 282 | throw err |
@@ -342,7 +344,7 @@ function updateVideo (req: express.Request, res: express.Response) { | |||
342 | }) | 344 | }) |
343 | }) | 345 | }) |
344 | .then(() => { | 346 | .then(() => { |
345 | logger.info('Video with name %s updated.', videoInstance.name) | 347 | logger.info('Video with name %s and uuid %s updated.', videoInstance.name, videoInstance.uuid) |
346 | }) | 348 | }) |
347 | .catch(err => { | 349 | .catch(err => { |
348 | logger.debug('Cannot update the video.', err) | 350 | logger.debug('Cannot update the video.', err) |
@@ -398,7 +400,10 @@ function removeVideo (req: express.Request, res: express.Response, next: express | |||
398 | const videoInstance = res.locals.video | 400 | const videoInstance = res.locals.video |
399 | 401 | ||
400 | videoInstance.destroy() | 402 | videoInstance.destroy() |
401 | .then(() => res.type('json').status(204).end()) | 403 | .then(() => { |
404 | logger.info('Video with name %s and uuid %s deleted.', videoInstance.name, videoInstance.uuid) | ||
405 | res.type('json').status(204).end() | ||
406 | }) | ||
402 | .catch(err => { | 407 | .catch(err => { |
403 | logger.error('Errors when removed the video.', err) | 408 | logger.error('Errors when removed the video.', err) |
404 | return next(err) | 409 | return next(err) |
diff --git a/server/middlewares/validators/videos.ts b/server/middlewares/validators/videos.ts index 249da668d..519e3d46c 100644 --- a/server/middlewares/validators/videos.ts +++ b/server/middlewares/validators/videos.ts | |||
@@ -109,8 +109,6 @@ function videosRemoveValidator (req: express.Request, res: express.Response, nex | |||
109 | 109 | ||
110 | checkErrors(req, res, () => { | 110 | checkErrors(req, res, () => { |
111 | checkVideoExists(req.params.id, res, () => { | 111 | checkVideoExists(req.params.id, res, () => { |
112 | // We need to make additional checks | ||
113 | |||
114 | // Check if the user who did the request is able to delete the video | 112 | // Check if the user who did the request is able to delete the video |
115 | checkUserCanDeleteVideo(res.locals.oauth.token.User.id, res, () => { | 113 | checkUserCanDeleteVideo(res.locals.oauth.token.User.id, res, () => { |
116 | next() | 114 | next() |
@@ -205,17 +203,15 @@ function checkUserCanDeleteVideo (userId: number, res: express.Response, callbac | |||
205 | // Retrieve the user who did the request | 203 | // Retrieve the user who did the request |
206 | db.User.loadById(userId) | 204 | db.User.loadById(userId) |
207 | .then(user => { | 205 | .then(user => { |
206 | if (res.locals.video.isOwned() === false) { | ||
207 | return res.status(403).send('Cannot remove video of another pod, blacklist it') | ||
208 | } | ||
209 | |||
208 | // Check if the user can delete the video | 210 | // Check if the user can delete the video |
209 | // The user can delete it if s/he is an admin | 211 | // The user can delete it if s/he is an admin |
210 | // Or if s/he is the video's author | 212 | // Or if s/he is the video's author |
211 | if (user.isAdmin() === false) { | 213 | if (user.isAdmin() === false && res.locals.video.Author.userId !== res.locals.oauth.token.User.id) { |
212 | if (res.locals.video.isOwned() === false) { | 214 | return res.status(403).send('Cannot remove video of another user') |
213 | return res.status(403).send('Cannot remove video of another pod') | ||
214 | } | ||
215 | |||
216 | if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) { | ||
217 | return res.status(403).send('Cannot remove video of another user') | ||
218 | } | ||
219 | } | 215 | } |
220 | 216 | ||
221 | // If we reach this comment, we can delete the video | 217 | // If we reach this comment, we can delete the video |
diff --git a/server/tests/real-world/real-world.ts b/server/tests/real-world/real-world.ts index a200bd02d..e225a1266 100644 --- a/server/tests/real-world/real-world.ts +++ b/server/tests/real-world/real-world.ts | |||
@@ -1,11 +1,10 @@ | |||
1 | import * as program from 'commander' | 1 | import * as program from 'commander' |
2 | import { isEqual, differenceWith } from 'lodash' | ||
3 | 2 | ||
4 | // /!\ Before imports /!\ | 3 | // /!\ Before imports /!\ |
5 | process.env.NODE_ENV = 'test' | 4 | process.env.NODE_ENV = 'test' |
6 | 5 | ||
7 | import { REQUESTS_INTERVAL } from '../../initializers/constants' | 6 | import { REQUESTS_INTERVAL } from '../../initializers/constants' |
8 | import { Video, VideoRateType } from '../../../shared' | 7 | import { Video, VideoRateType, VideoFile } from '../../../shared' |
9 | import { | 8 | import { |
10 | ServerInfo as DefaultServerInfo, | 9 | ServerInfo as DefaultServerInfo, |
11 | flushAndRunMultipleServers, | 10 | flushAndRunMultipleServers, |
@@ -121,12 +120,14 @@ async function start () { | |||
121 | checking = true | 120 | checking = true |
122 | 121 | ||
123 | const waitingInterval = setInterval(async () => { | 122 | const waitingInterval = setInterval(async () => { |
124 | const awaitingRequests = await isThereAwaitingRequests(servers) | 123 | const pendingRequests = await isTherePendingRequests(servers) |
125 | if (awaitingRequests === true) { | 124 | if (pendingRequests === true) { |
126 | console.log('A server has awaiting requests, waiting...') | 125 | console.log('A server has pending requests, waiting...') |
127 | return | 126 | return |
128 | } | 127 | } |
129 | 128 | ||
129 | // Even if there are no pending request, wait some potential processes | ||
130 | await wait(2000) | ||
130 | await checkIntegrity(servers) | 131 | await checkIntegrity(servers) |
131 | 132 | ||
132 | initializeRequestsPerServer(servers) | 133 | initializeRequestsPerServer(servers) |
@@ -212,7 +213,7 @@ async function update (servers: ServerInfo[], numServer: number) { | |||
212 | 213 | ||
213 | async function remove (servers: ServerInfo[], numServer: number) { | 214 | async function remove (servers: ServerInfo[], numServer: number) { |
214 | const res = await getVideosList(servers[numServer].url) | 215 | const res = await getVideosList(servers[numServer].url) |
215 | const videos = res.body.data | 216 | const videos = res.body.data.filter(video => video.isLocal === true) |
216 | if (videos.length === 0) return undefined | 217 | if (videos.length === 0) return undefined |
217 | 218 | ||
218 | const toRemove = videos[getRandomInt(0, videos.length)].id | 219 | const toRemove = videos[getRandomInt(0, videos.length)].id |
@@ -259,19 +260,7 @@ async function checkIntegrity (servers: ServerInfo[]) { | |||
259 | 260 | ||
260 | // Fetch all videos and remove some fields that can differ between pods | 261 | // Fetch all videos and remove some fields that can differ between pods |
261 | for (const server of servers) { | 262 | for (const server of servers) { |
262 | const p = getAllVideosListBy(server.url).then(res => { | 263 | const p = getAllVideosListBy(server.url).then(res => videos.push(res.body.data)) |
263 | const serverVideos = res.body.data | ||
264 | for (const serverVideo of serverVideos) { | ||
265 | delete serverVideo.id | ||
266 | delete serverVideo.isLocal | ||
267 | delete serverVideo.thumbnailPath | ||
268 | delete serverVideo.updatedAt | ||
269 | delete serverVideo.views | ||
270 | } | ||
271 | |||
272 | videos.push(serverVideos) | ||
273 | }) | ||
274 | |||
275 | tasks.push(p) | 264 | tasks.push(p) |
276 | } | 265 | } |
277 | 266 | ||
@@ -279,12 +268,12 @@ async function checkIntegrity (servers: ServerInfo[]) { | |||
279 | 268 | ||
280 | let i = 0 | 269 | let i = 0 |
281 | for (const video of videos) { | 270 | for (const video of videos) { |
282 | if (!isEqual(video, videos[0])) { | 271 | const differences = areDifferences(video, videos[0]) |
272 | if (differences !== undefined) { | ||
283 | console.error('Integrity not ok with server %d!', i + 1) | 273 | console.error('Integrity not ok with server %d!', i + 1) |
284 | 274 | ||
285 | if (displayDiffOnFail) { | 275 | if (displayDiffOnFail) { |
286 | console.log(differenceWith(videos[0], video, isEqual)) | 276 | console.log(differences) |
287 | console.log(differenceWith(video, videos[0], isEqual)) | ||
288 | } | 277 | } |
289 | 278 | ||
290 | process.exit(-1) | 279 | process.exit(-1) |
@@ -296,15 +285,74 @@ async function checkIntegrity (servers: ServerInfo[]) { | |||
296 | console.log('Integrity ok.') | 285 | console.log('Integrity ok.') |
297 | } | 286 | } |
298 | 287 | ||
288 | function areDifferences (videos1: Video[], videos2: Video[]) { | ||
289 | // Remove some keys we don't want to compare | ||
290 | videos1.concat(videos2).forEach(video => { | ||
291 | delete video.id | ||
292 | delete video.isLocal | ||
293 | delete video.thumbnailPath | ||
294 | delete video.updatedAt | ||
295 | delete video.views | ||
296 | }) | ||
297 | |||
298 | if (videos1.length !== videos2.length) { | ||
299 | return `Videos length are different (${videos1.length}/${videos2.length}).` | ||
300 | } | ||
301 | |||
302 | for (const video1 of videos1) { | ||
303 | const video2 = videos2.find(video => video.uuid === video1.uuid) | ||
304 | |||
305 | if (!video2) return 'Video ' + video1.uuid + ' is missing.' | ||
306 | |||
307 | for (const videoKey of Object.keys(video1)) { | ||
308 | const attribute1 = video1[videoKey] | ||
309 | const attribute2 = video2[videoKey] | ||
310 | |||
311 | if (videoKey === 'tags') { | ||
312 | if (attribute1.length !== attribute2.length) { | ||
313 | return 'Tags are different.' | ||
314 | } | ||
315 | |||
316 | attribute1.forEach(tag1 => { | ||
317 | if (attribute2.indexOf(tag1) === -1) { | ||
318 | return 'Tag ' + tag1 + ' is missing.' | ||
319 | } | ||
320 | }) | ||
321 | } else if (videoKey === 'files') { | ||
322 | if (attribute1.length !== attribute2.length) { | ||
323 | return 'Video files are different.' | ||
324 | } | ||
325 | |||
326 | attribute1.forEach((videoFile1: VideoFile) => { | ||
327 | const videoFile2: VideoFile = attribute2.find(videoFile => videoFile.magnetUri === videoFile1.magnetUri) | ||
328 | if (!videoFile2) { | ||
329 | return `Video ${video1.uuid} has missing video file ${videoFile1.magnetUri}.` | ||
330 | } | ||
331 | |||
332 | if (videoFile1.size !== videoFile2.size || videoFile1.resolutionLabel !== videoFile2.resolutionLabel) { | ||
333 | return `Video ${video1.uuid} has different video file ${videoFile1.magnetUri}.` | ||
334 | } | ||
335 | }) | ||
336 | } else { | ||
337 | if (attribute1 !== attribute2) { | ||
338 | return `Video ${video1.uuid} has different value for attribute ${videoKey}.` | ||
339 | } | ||
340 | } | ||
341 | } | ||
342 | } | ||
343 | |||
344 | return undefined | ||
345 | } | ||
346 | |||
299 | function goodbye () { | 347 | function goodbye () { |
300 | return process.exit(-1) | 348 | return process.exit(-1) |
301 | } | 349 | } |
302 | 350 | ||
303 | async function isThereAwaitingRequests (servers: ServerInfo[]) { | 351 | async function isTherePendingRequests (servers: ServerInfo[]) { |
304 | const tasks: Promise<any>[] = [] | 352 | const tasks: Promise<any>[] = [] |
305 | let awaitingRequests = false | 353 | let pendingRequests = false |
306 | 354 | ||
307 | // Check if each server has awaiting request | 355 | // Check if each server has pending request |
308 | for (const server of servers) { | 356 | for (const server of servers) { |
309 | const p = getRequestsStats(server).then(res => { | 357 | const p = getRequestsStats(server).then(res => { |
310 | const stats = res.body | 358 | const stats = res.body |
@@ -314,7 +362,7 @@ async function isThereAwaitingRequests (servers: ServerInfo[]) { | |||
314 | stats.requestVideoEventScheduler.totalRequests !== 0 || | 362 | stats.requestVideoEventScheduler.totalRequests !== 0 || |
315 | stats.requestVideoQaduScheduler.totalRequests !== 0 | 363 | stats.requestVideoQaduScheduler.totalRequests !== 0 |
316 | ) { | 364 | ) { |
317 | awaitingRequests = true | 365 | pendingRequests = true |
318 | } | 366 | } |
319 | }) | 367 | }) |
320 | 368 | ||
@@ -323,5 +371,5 @@ async function isThereAwaitingRequests (servers: ServerInfo[]) { | |||
323 | 371 | ||
324 | await Promise.all(tasks) | 372 | await Promise.all(tasks) |
325 | 373 | ||
326 | return awaitingRequests | 374 | return pendingRequests |
327 | } | 375 | } |