]>
Commit | Line | Data |
---|---|---|
4d4e5cd4 | 1 | import * as express from 'express' |
6fcd19ba | 2 | import * as Promise from 'bluebird' |
72c7248b | 3 | import * as Sequelize from 'sequelize' |
528a9efa | 4 | |
e02643f3 | 5 | import { database as db } from '../../../initializers/database' |
65fcc311 C |
6 | import { |
7 | REQUEST_ENDPOINT_ACTIONS, | |
8 | REQUEST_ENDPOINTS, | |
9 | REQUEST_VIDEO_EVENT_TYPES, | |
10 | REQUEST_VIDEO_QADU_TYPES | |
11 | } from '../../../initializers' | |
12 | import { | |
13 | checkSignature, | |
14 | signatureValidator, | |
15 | remoteVideosValidator, | |
16 | remoteQaduVideosValidator, | |
17 | remoteEventsVideosValidator | |
18 | } from '../../../middlewares' | |
6fcd19ba | 19 | import { logger, retryTransactionWrapper } from '../../../helpers' |
65fcc311 | 20 | import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib' |
6d33593a | 21 | import { PodInstance, VideoFileInstance } from '../../../models' |
4771e000 C |
22 | import { |
23 | RemoteVideoRequest, | |
24 | RemoteVideoCreateData, | |
25 | RemoteVideoUpdateData, | |
26 | RemoteVideoRemoveData, | |
27 | RemoteVideoReportAbuseData, | |
28 | RemoteQaduVideoRequest, | |
29 | RemoteQaduVideoData, | |
30 | RemoteVideoEventRequest, | |
72c7248b C |
31 | RemoteVideoEventData, |
32 | RemoteVideoChannelCreateData, | |
33 | RemoteVideoChannelUpdateData, | |
34 | RemoteVideoChannelRemoveData, | |
35 | RemoteVideoAuthorRemoveData, | |
36 | RemoteVideoAuthorCreateData | |
4771e000 | 37 | } from '../../../../shared' |
65fcc311 C |
38 | |
39 | const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS] | |
62f4ef41 C |
40 | |
41 | // Functions to call when processing a remote request | |
72c7248b | 42 | // FIXME: use RemoteVideoRequestType as id type |
6fcd19ba | 43 | const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {} |
72c7248b C |
44 | functionsHash[ENDPOINT_ACTIONS.ADD_VIDEO] = addRemoteVideoRetryWrapper |
45 | functionsHash[ENDPOINT_ACTIONS.UPDATE_VIDEO] = updateRemoteVideoRetryWrapper | |
46 | functionsHash[ENDPOINT_ACTIONS.REMOVE_VIDEO] = removeRemoteVideoRetryWrapper | |
47 | functionsHash[ENDPOINT_ACTIONS.ADD_CHANNEL] = addRemoteVideoChannelRetryWrapper | |
48 | functionsHash[ENDPOINT_ACTIONS.UPDATE_CHANNEL] = updateRemoteVideoChannelRetryWrapper | |
49 | functionsHash[ENDPOINT_ACTIONS.REMOVE_CHANNEL] = removeRemoteVideoChannelRetryWrapper | |
50 | functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideoRetryWrapper | |
51 | functionsHash[ENDPOINT_ACTIONS.ADD_AUTHOR] = addRemoteVideoAuthorRetryWrapper | |
52 | functionsHash[ENDPOINT_ACTIONS.REMOVE_AUTHOR] = removeRemoteVideoAuthorRetryWrapper | |
62f4ef41 | 53 | |
65fcc311 | 54 | const remoteVideosRouter = express.Router() |
528a9efa | 55 | |
65fcc311 C |
56 | remoteVideosRouter.post('/', |
57 | signatureValidator, | |
58 | checkSignature, | |
59 | remoteVideosValidator, | |
528a9efa C |
60 | remoteVideos |
61 | ) | |
62 | ||
65fcc311 C |
63 | remoteVideosRouter.post('/qadu', |
64 | signatureValidator, | |
65 | checkSignature, | |
66 | remoteQaduVideosValidator, | |
9e167724 C |
67 | remoteVideosQadu |
68 | ) | |
69 | ||
65fcc311 C |
70 | remoteVideosRouter.post('/events', |
71 | signatureValidator, | |
72 | checkSignature, | |
73 | remoteEventsVideosValidator, | |
e4c87ec2 C |
74 | remoteVideosEvents |
75 | ) | |
76 | ||
528a9efa C |
77 | // --------------------------------------------------------------------------- |
78 | ||
65fcc311 C |
79 | export { |
80 | remoteVideosRouter | |
81 | } | |
528a9efa C |
82 | |
83 | // --------------------------------------------------------------------------- | |
84 | ||
69818c93 | 85 | function remoteVideos (req: express.Request, res: express.Response, next: express.NextFunction) { |
4771e000 | 86 | const requests: RemoteVideoRequest[] = req.body.data |
4ff0d862 | 87 | const fromPod = res.locals.secure.pod |
528a9efa C |
88 | |
89 | // We need to process in the same order to keep consistency | |
4771e000 | 90 | Promise.each(requests, request => { |
55fa55a9 | 91 | const data = request.data |
528a9efa | 92 | |
62f4ef41 C |
93 | // Get the function we need to call in order to process the request |
94 | const fun = functionsHash[request.type] | |
95 | if (fun === undefined) { | |
6d33593a | 96 | logger.error('Unknown remote request type %s.', request.type) |
6fcd19ba | 97 | return |
528a9efa | 98 | } |
62f4ef41 | 99 | |
6fcd19ba | 100 | return fun.call(this, data, fromPod) |
528a9efa | 101 | }) |
ad0997ad | 102 | .catch(err => logger.error('Error managing remote videos.', err)) |
528a9efa | 103 | |
709756b8 | 104 | // Don't block the other pod |
528a9efa C |
105 | return res.type('json').status(204).end() |
106 | } | |
107 | ||
69818c93 | 108 | function remoteVideosQadu (req: express.Request, res: express.Response, next: express.NextFunction) { |
4771e000 | 109 | const requests: RemoteQaduVideoRequest[] = req.body.data |
9e167724 C |
110 | const fromPod = res.locals.secure.pod |
111 | ||
4771e000 | 112 | Promise.each(requests, request => { |
9e167724 C |
113 | const videoData = request.data |
114 | ||
6fcd19ba | 115 | return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod) |
9e167724 | 116 | }) |
ad0997ad | 117 | .catch(err => logger.error('Error managing remote videos.', err)) |
9e167724 C |
118 | |
119 | return res.type('json').status(204).end() | |
120 | } | |
121 | ||
69818c93 | 122 | function remoteVideosEvents (req: express.Request, res: express.Response, next: express.NextFunction) { |
4771e000 | 123 | const requests: RemoteVideoEventRequest[] = req.body.data |
e4c87ec2 C |
124 | const fromPod = res.locals.secure.pod |
125 | ||
4771e000 | 126 | Promise.each(requests, request => { |
e4c87ec2 C |
127 | const eventData = request.data |
128 | ||
6fcd19ba | 129 | return processVideosEventsRetryWrapper(eventData, fromPod) |
e4c87ec2 | 130 | }) |
ad0997ad | 131 | .catch(err => logger.error('Error managing remote videos.', err)) |
e4c87ec2 C |
132 | |
133 | return res.type('json').status(204).end() | |
134 | } | |
135 | ||
4771e000 | 136 | function processVideosEventsRetryWrapper (eventData: RemoteVideoEventData, fromPod: PodInstance) { |
e4c87ec2 C |
137 | const options = { |
138 | arguments: [ eventData, fromPod ], | |
139 | errorMessage: 'Cannot process videos events with many retries.' | |
140 | } | |
141 | ||
6fcd19ba | 142 | return retryTransactionWrapper(processVideosEvents, options) |
e4c87ec2 C |
143 | } |
144 | ||
4771e000 | 145 | function processVideosEvents (eventData: RemoteVideoEventData, fromPod: PodInstance) { |
e4c87ec2 | 146 | |
6fcd19ba | 147 | return db.sequelize.transaction(t => { |
72c7248b | 148 | return fetchVideoByUUID(eventData.uuid, t) |
6fcd19ba C |
149 | .then(videoInstance => { |
150 | const options = { transaction: t } | |
e4c87ec2 | 151 | |
6fcd19ba C |
152 | let columnToUpdate |
153 | let qaduType | |
e4c87ec2 | 154 | |
6fcd19ba | 155 | switch (eventData.eventType) { |
980246ea C |
156 | case REQUEST_VIDEO_EVENT_TYPES.VIEWS: |
157 | columnToUpdate = 'views' | |
158 | qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS | |
159 | break | |
160 | ||
161 | case REQUEST_VIDEO_EVENT_TYPES.LIKES: | |
162 | columnToUpdate = 'likes' | |
163 | qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES | |
164 | break | |
165 | ||
166 | case REQUEST_VIDEO_EVENT_TYPES.DISLIKES: | |
167 | columnToUpdate = 'dislikes' | |
168 | qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES | |
169 | break | |
170 | ||
171 | default: | |
172 | throw new Error('Unknown video event type.') | |
6fcd19ba | 173 | } |
e4c87ec2 | 174 | |
6fcd19ba C |
175 | const query = {} |
176 | query[columnToUpdate] = eventData.count | |
e4c87ec2 | 177 | |
6fcd19ba | 178 | return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType })) |
d38b8281 | 179 | }) |
6fcd19ba C |
180 | .then(({ videoInstance, qaduType }) => { |
181 | const qadusParams = [ | |
182 | { | |
183 | videoId: videoInstance.id, | |
184 | type: qaduType | |
185 | } | |
186 | ] | |
187 | ||
188 | return quickAndDirtyUpdatesVideoToFriends(qadusParams, t) | |
e4c87ec2 | 189 | }) |
6fcd19ba | 190 | }) |
6d33593a | 191 | .then(() => logger.info('Remote video event processed for video with uuid %s.', eventData.uuid)) |
6fcd19ba | 192 | .catch(err => { |
ad0997ad | 193 | logger.debug('Cannot process a video event.', err) |
6fcd19ba | 194 | throw err |
e4c87ec2 C |
195 | }) |
196 | } | |
197 | ||
4771e000 | 198 | function quickAndDirtyUpdateVideoRetryWrapper (videoData: RemoteQaduVideoData, fromPod: PodInstance) { |
9e167724 C |
199 | const options = { |
200 | arguments: [ videoData, fromPod ], | |
201 | errorMessage: 'Cannot update quick and dirty the remote video with many retries.' | |
202 | } | |
203 | ||
6fcd19ba | 204 | return retryTransactionWrapper(quickAndDirtyUpdateVideo, options) |
9e167724 C |
205 | } |
206 | ||
4771e000 | 207 | function quickAndDirtyUpdateVideo (videoData: RemoteQaduVideoData, fromPod: PodInstance) { |
6d33593a | 208 | let videoUUID = '' |
f148e5ed | 209 | |
6fcd19ba | 210 | return db.sequelize.transaction(t => { |
72c7248b | 211 | return fetchVideoByHostAndUUID(fromPod.host, videoData.uuid, t) |
6fcd19ba C |
212 | .then(videoInstance => { |
213 | const options = { transaction: t } | |
9e167724 | 214 | |
6d33593a | 215 | videoUUID = videoInstance.uuid |
f148e5ed | 216 | |
6fcd19ba C |
217 | if (videoData.views) { |
218 | videoInstance.set('views', videoData.views) | |
219 | } | |
9e167724 | 220 | |
6fcd19ba C |
221 | if (videoData.likes) { |
222 | videoInstance.set('likes', videoData.likes) | |
223 | } | |
9e167724 | 224 | |
6fcd19ba C |
225 | if (videoData.dislikes) { |
226 | videoInstance.set('dislikes', videoData.dislikes) | |
227 | } | |
9e167724 | 228 | |
6fcd19ba | 229 | return videoInstance.save(options) |
9e167724 | 230 | }) |
9e167724 | 231 | }) |
6d33593a | 232 | .then(() => logger.info('Remote video with uuid %s quick and dirty updated', videoUUID)) |
ad0997ad | 233 | .catch(err => logger.debug('Cannot quick and dirty update the remote video.', err)) |
9e167724 C |
234 | } |
235 | ||
ed04d94f | 236 | // Handle retries on fail |
4771e000 | 237 | function addRemoteVideoRetryWrapper (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) { |
d6a5b018 C |
238 | const options = { |
239 | arguments: [ videoToCreateData, fromPod ], | |
240 | errorMessage: 'Cannot insert the remote video with many retries.' | |
241 | } | |
ed04d94f | 242 | |
6fcd19ba | 243 | return retryTransactionWrapper(addRemoteVideo, options) |
ed04d94f C |
244 | } |
245 | ||
4771e000 | 246 | function addRemoteVideo (videoToCreateData: RemoteVideoCreateData, fromPod: PodInstance) { |
0a6658fd | 247 | logger.debug('Adding remote video "%s".', videoToCreateData.uuid) |
6666aad4 | 248 | |
6fcd19ba | 249 | return db.sequelize.transaction(t => { |
0a6658fd | 250 | return db.Video.loadByUUID(videoToCreateData.uuid) |
6fcd19ba | 251 | .then(video => { |
0a6658fd | 252 | if (video) throw new Error('UUID already exists.') |
cddadde8 | 253 | |
72c7248b | 254 | return db.VideoChannel.loadByHostAndUUID(fromPod.host, videoToCreateData.channelUUID, t) |
cddadde8 | 255 | }) |
72c7248b C |
256 | .then(videoChannel => { |
257 | if (!videoChannel) throw new Error('Video channel ' + videoToCreateData.channelUUID + ' not found.') | |
feb4bdfd | 258 | |
6fcd19ba | 259 | const tags = videoToCreateData.tags |
feb4bdfd | 260 | |
72c7248b | 261 | return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoChannel, tagInstances })) |
7920c273 | 262 | }) |
72c7248b | 263 | .then(({ videoChannel, tagInstances }) => { |
6fcd19ba C |
264 | const videoData = { |
265 | name: videoToCreateData.name, | |
0a6658fd | 266 | uuid: videoToCreateData.uuid, |
6fcd19ba C |
267 | category: videoToCreateData.category, |
268 | licence: videoToCreateData.licence, | |
269 | language: videoToCreateData.language, | |
270 | nsfw: videoToCreateData.nsfw, | |
271 | description: videoToCreateData.description, | |
72c7248b | 272 | channelId: videoChannel.id, |
6fcd19ba C |
273 | duration: videoToCreateData.duration, |
274 | createdAt: videoToCreateData.createdAt, | |
275 | // FIXME: updatedAt does not seems to be considered by Sequelize | |
276 | updatedAt: videoToCreateData.updatedAt, | |
277 | views: videoToCreateData.views, | |
278 | likes: videoToCreateData.likes, | |
0a6658fd C |
279 | dislikes: videoToCreateData.dislikes, |
280 | remote: true | |
feb4bdfd C |
281 | } |
282 | ||
6fcd19ba C |
283 | const video = db.Video.build(videoData) |
284 | return { tagInstances, video } | |
feb4bdfd | 285 | }) |
6fcd19ba C |
286 | .then(({ tagInstances, video }) => { |
287 | return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video })) | |
7920c273 | 288 | }) |
6fcd19ba C |
289 | .then(({ tagInstances, video }) => { |
290 | const options = { | |
291 | transaction: t | |
292 | } | |
7920c273 | 293 | |
6fcd19ba | 294 | return video.save(options).then(videoCreated => ({ tagInstances, videoCreated })) |
7920c273 | 295 | }) |
93e1258c C |
296 | .then(({ tagInstances, videoCreated }) => { |
297 | const tasks = [] | |
298 | const options = { | |
299 | transaction: t | |
300 | } | |
301 | ||
302 | videoToCreateData.files.forEach(fileData => { | |
303 | const videoFileInstance = db.VideoFile.build({ | |
304 | extname: fileData.extname, | |
305 | infoHash: fileData.infoHash, | |
306 | resolution: fileData.resolution, | |
307 | size: fileData.size, | |
308 | videoId: videoCreated.id | |
309 | }) | |
310 | ||
311 | tasks.push(videoFileInstance.save(options)) | |
312 | }) | |
313 | ||
314 | return Promise.all(tasks).then(() => ({ tagInstances, videoCreated })) | |
315 | }) | |
6fcd19ba C |
316 | .then(({ tagInstances, videoCreated }) => { |
317 | const options = { | |
318 | transaction: t | |
319 | } | |
7920c273 | 320 | |
6fcd19ba C |
321 | return videoCreated.setTags(tagInstances, options) |
322 | }) | |
323 | }) | |
6d33593a | 324 | .then(() => logger.info('Remote video with uuid %s inserted.', videoToCreateData.uuid)) |
6fcd19ba | 325 | .catch(err => { |
ad0997ad | 326 | logger.debug('Cannot insert the remote video.', err) |
6fcd19ba | 327 | throw err |
7920c273 | 328 | }) |
528a9efa C |
329 | } |
330 | ||
ed04d94f | 331 | // Handle retries on fail |
4771e000 | 332 | function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) { |
d6a5b018 | 333 | const options = { |
fbc22d79 | 334 | arguments: [ videoAttributesToUpdate, fromPod ], |
d6a5b018 C |
335 | errorMessage: 'Cannot update the remote video with many retries' |
336 | } | |
ed04d94f | 337 | |
6fcd19ba | 338 | return retryTransactionWrapper(updateRemoteVideo, options) |
ed04d94f C |
339 | } |
340 | ||
4771e000 | 341 | function updateRemoteVideo (videoAttributesToUpdate: RemoteVideoUpdateData, fromPod: PodInstance) { |
0a6658fd | 342 | logger.debug('Updating remote video "%s".', videoAttributesToUpdate.uuid) |
feb4bdfd | 343 | |
6fcd19ba | 344 | return db.sequelize.transaction(t => { |
72c7248b | 345 | return fetchVideoByHostAndUUID(fromPod.host, videoAttributesToUpdate.uuid, t) |
6fcd19ba C |
346 | .then(videoInstance => { |
347 | const tags = videoAttributesToUpdate.tags | |
3d118fb5 | 348 | |
6fcd19ba | 349 | return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances })) |
3d118fb5 | 350 | }) |
6fcd19ba C |
351 | .then(({ videoInstance, tagInstances }) => { |
352 | const options = { transaction: t } | |
353 | ||
354 | videoInstance.set('name', videoAttributesToUpdate.name) | |
355 | videoInstance.set('category', videoAttributesToUpdate.category) | |
356 | videoInstance.set('licence', videoAttributesToUpdate.licence) | |
357 | videoInstance.set('language', videoAttributesToUpdate.language) | |
358 | videoInstance.set('nsfw', videoAttributesToUpdate.nsfw) | |
359 | videoInstance.set('description', videoAttributesToUpdate.description) | |
6fcd19ba C |
360 | videoInstance.set('duration', videoAttributesToUpdate.duration) |
361 | videoInstance.set('createdAt', videoAttributesToUpdate.createdAt) | |
362 | videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt) | |
6fcd19ba C |
363 | videoInstance.set('views', videoAttributesToUpdate.views) |
364 | videoInstance.set('likes', videoAttributesToUpdate.likes) | |
365 | videoInstance.set('dislikes', videoAttributesToUpdate.dislikes) | |
366 | ||
367 | return videoInstance.save(options).then(() => ({ videoInstance, tagInstances })) | |
3d118fb5 | 368 | }) |
93e1258c | 369 | .then(({ tagInstances, videoInstance }) => { |
6d33593a C |
370 | const tasks: Promise<void>[] = [] |
371 | ||
372 | // Remove old video files | |
373 | videoInstance.VideoFiles.forEach(videoFile => { | |
72c7248b | 374 | tasks.push(videoFile.destroy({ transaction: t })) |
6d33593a C |
375 | }) |
376 | ||
377 | return Promise.all(tasks).then(() => ({ tagInstances, videoInstance })) | |
378 | }) | |
379 | .then(({ tagInstances, videoInstance }) => { | |
380 | const tasks: Promise<VideoFileInstance>[] = [] | |
93e1258c C |
381 | const options = { |
382 | transaction: t | |
383 | } | |
384 | ||
385 | videoAttributesToUpdate.files.forEach(fileData => { | |
386 | const videoFileInstance = db.VideoFile.build({ | |
387 | extname: fileData.extname, | |
388 | infoHash: fileData.infoHash, | |
389 | resolution: fileData.resolution, | |
390 | size: fileData.size, | |
391 | videoId: videoInstance.id | |
392 | }) | |
393 | ||
394 | tasks.push(videoFileInstance.save(options)) | |
395 | }) | |
396 | ||
397 | return Promise.all(tasks).then(() => ({ tagInstances, videoInstance })) | |
398 | }) | |
6fcd19ba C |
399 | .then(({ videoInstance, tagInstances }) => { |
400 | const options = { transaction: t } | |
3d118fb5 | 401 | |
6fcd19ba | 402 | return videoInstance.setTags(tagInstances, options) |
3d118fb5 | 403 | }) |
6fcd19ba | 404 | }) |
6d33593a | 405 | .then(() => logger.info('Remote video with uuid %s updated', videoAttributesToUpdate.uuid)) |
6fcd19ba C |
406 | .catch(err => { |
407 | // This is just a debug because we will retry the insert | |
ad0997ad | 408 | logger.debug('Cannot update the remote video.', err) |
6fcd19ba | 409 | throw err |
3d118fb5 C |
410 | }) |
411 | } | |
412 | ||
72c7248b C |
413 | function removeRemoteVideoRetryWrapper (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) { |
414 | const options = { | |
415 | arguments: [ videoToRemoveData, fromPod ], | |
416 | errorMessage: 'Cannot remove the remote video channel with many retries.' | |
417 | } | |
418 | ||
419 | return retryTransactionWrapper(removeRemoteVideo, options) | |
420 | } | |
421 | ||
4771e000 | 422 | function removeRemoteVideo (videoToRemoveData: RemoteVideoRemoveData, fromPod: PodInstance) { |
72c7248b C |
423 | logger.debug('Removing remote video "%s".', videoToRemoveData.uuid) |
424 | ||
425 | return db.sequelize.transaction(t => { | |
426 | // We need the instance because we have to remove some other stuffs (thumbnail etc) | |
427 | return fetchVideoByHostAndUUID(fromPod.host, videoToRemoveData.uuid, t) | |
428 | .then(video => video.destroy({ transaction: t })) | |
429 | }) | |
430 | .then(() => logger.info('Remote video with uuid %s removed.', videoToRemoveData.uuid)) | |
431 | .catch(err => { | |
432 | logger.debug('Cannot remove the remote video.', err) | |
433 | throw err | |
434 | }) | |
435 | } | |
436 | ||
437 | function addRemoteVideoAuthorRetryWrapper (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) { | |
438 | const options = { | |
439 | arguments: [ authorToCreateData, fromPod ], | |
440 | errorMessage: 'Cannot insert the remote video author with many retries.' | |
441 | } | |
442 | ||
443 | return retryTransactionWrapper(addRemoteVideoAuthor, options) | |
444 | } | |
445 | ||
446 | function addRemoteVideoAuthor (authorToCreateData: RemoteVideoAuthorCreateData, fromPod: PodInstance) { | |
447 | logger.debug('Adding remote video author "%s".', authorToCreateData.uuid) | |
448 | ||
449 | return db.sequelize.transaction(t => { | |
450 | return db.Author.loadAuthorByPodAndUUID(authorToCreateData.uuid, fromPod.id, t) | |
451 | .then(author => { | |
452 | if (author) throw new Error('UUID already exists.') | |
453 | ||
454 | return undefined | |
455 | }) | |
456 | .then(() => { | |
457 | const videoAuthorData = { | |
458 | name: authorToCreateData.name, | |
459 | uuid: authorToCreateData.uuid, | |
460 | userId: null, // Not on our pod | |
461 | podId: fromPod.id | |
462 | } | |
463 | ||
464 | const author = db.Author.build(videoAuthorData) | |
465 | return author.save({ transaction: t }) | |
466 | }) | |
467 | }) | |
468 | .then(() => logger.info('Remote video author with uuid %s inserted.', authorToCreateData.uuid)) | |
6fcd19ba | 469 | .catch(err => { |
72c7248b C |
470 | logger.debug('Cannot insert the remote video author.', err) |
471 | throw err | |
d8cc063e | 472 | }) |
55fa55a9 C |
473 | } |
474 | ||
72c7248b C |
475 | function removeRemoteVideoAuthorRetryWrapper (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) { |
476 | const options = { | |
477 | arguments: [ authorAttributesToRemove, fromPod ], | |
478 | errorMessage: 'Cannot remove the remote video author with many retries.' | |
479 | } | |
480 | ||
481 | return retryTransactionWrapper(removeRemoteVideoAuthor, options) | |
482 | } | |
483 | ||
484 | function removeRemoteVideoAuthor (authorAttributesToRemove: RemoteVideoAuthorRemoveData, fromPod: PodInstance) { | |
485 | logger.debug('Removing remote video author "%s".', authorAttributesToRemove.uuid) | |
486 | ||
487 | return db.sequelize.transaction(t => { | |
488 | return db.Author.loadAuthorByPodAndUUID(authorAttributesToRemove.uuid, fromPod.id, t) | |
489 | .then(videoAuthor => videoAuthor.destroy({ transaction: t })) | |
490 | }) | |
491 | .then(() => logger.info('Remote video author with uuid %s removed.', authorAttributesToRemove.uuid)) | |
492 | .catch(err => { | |
493 | logger.debug('Cannot remove the remote video author.', err) | |
494 | throw err | |
495 | }) | |
496 | } | |
497 | ||
498 | function addRemoteVideoChannelRetryWrapper (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) { | |
499 | const options = { | |
500 | arguments: [ videoChannelToCreateData, fromPod ], | |
501 | errorMessage: 'Cannot insert the remote video channel with many retries.' | |
502 | } | |
503 | ||
504 | return retryTransactionWrapper(addRemoteVideoChannel, options) | |
505 | } | |
506 | ||
507 | function addRemoteVideoChannel (videoChannelToCreateData: RemoteVideoChannelCreateData, fromPod: PodInstance) { | |
508 | logger.debug('Adding remote video channel "%s".', videoChannelToCreateData.uuid) | |
509 | ||
510 | return db.sequelize.transaction(t => { | |
511 | return db.VideoChannel.loadByUUID(videoChannelToCreateData.uuid) | |
512 | .then(videoChannel => { | |
513 | if (videoChannel) throw new Error('UUID already exists.') | |
514 | ||
515 | return undefined | |
516 | }) | |
517 | .then(() => { | |
518 | const authorUUID = videoChannelToCreateData.ownerUUID | |
519 | const podId = fromPod.id | |
520 | ||
521 | return db.Author.loadAuthorByPodAndUUID(authorUUID, podId, t) | |
522 | }) | |
523 | .then(author => { | |
524 | if (!author) throw new Error('Unknown author UUID.') | |
525 | ||
526 | const videoChannelData = { | |
527 | name: videoChannelToCreateData.name, | |
528 | description: videoChannelToCreateData.description, | |
529 | uuid: videoChannelToCreateData.uuid, | |
530 | createdAt: videoChannelToCreateData.createdAt, | |
531 | updatedAt: videoChannelToCreateData.updatedAt, | |
532 | remote: true, | |
533 | authorId: author.id | |
534 | } | |
535 | ||
536 | const videoChannel = db.VideoChannel.build(videoChannelData) | |
537 | return videoChannel.save({ transaction: t }) | |
538 | }) | |
539 | }) | |
540 | .then(() => logger.info('Remote video channel with uuid %s inserted.', videoChannelToCreateData.uuid)) | |
541 | .catch(err => { | |
542 | logger.debug('Cannot insert the remote video channel.', err) | |
543 | throw err | |
544 | }) | |
545 | } | |
546 | ||
547 | function updateRemoteVideoChannelRetryWrapper (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) { | |
548 | const options = { | |
549 | arguments: [ videoChannelAttributesToUpdate, fromPod ], | |
550 | errorMessage: 'Cannot update the remote video channel with many retries.' | |
551 | } | |
552 | ||
553 | return retryTransactionWrapper(updateRemoteVideoChannel, options) | |
554 | } | |
555 | ||
556 | function updateRemoteVideoChannel (videoChannelAttributesToUpdate: RemoteVideoChannelUpdateData, fromPod: PodInstance) { | |
557 | logger.debug('Updating remote video channel "%s".', videoChannelAttributesToUpdate.uuid) | |
558 | ||
559 | return db.sequelize.transaction(t => { | |
560 | return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToUpdate.uuid, t) | |
561 | .then(videoChannelInstance => { | |
562 | const options = { transaction: t } | |
563 | ||
564 | videoChannelInstance.set('name', videoChannelAttributesToUpdate.name) | |
565 | videoChannelInstance.set('description', videoChannelAttributesToUpdate.description) | |
566 | videoChannelInstance.set('createdAt', videoChannelAttributesToUpdate.createdAt) | |
567 | videoChannelInstance.set('updatedAt', videoChannelAttributesToUpdate.updatedAt) | |
568 | ||
569 | return videoChannelInstance.save(options) | |
570 | }) | |
571 | }) | |
572 | .then(() => logger.info('Remote video channel with uuid %s updated', videoChannelAttributesToUpdate.uuid)) | |
573 | .catch(err => { | |
574 | // This is just a debug because we will retry the insert | |
575 | logger.debug('Cannot update the remote video channel.', err) | |
576 | throw err | |
577 | }) | |
578 | } | |
579 | ||
580 | function removeRemoteVideoChannelRetryWrapper (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) { | |
581 | const options = { | |
582 | arguments: [ videoChannelAttributesToRemove, fromPod ], | |
583 | errorMessage: 'Cannot remove the remote video channel with many retries.' | |
584 | } | |
585 | ||
586 | return retryTransactionWrapper(removeRemoteVideoChannel, options) | |
587 | } | |
588 | ||
589 | function removeRemoteVideoChannel (videoChannelAttributesToRemove: RemoteVideoChannelRemoveData, fromPod: PodInstance) { | |
590 | logger.debug('Removing remote video channel "%s".', videoChannelAttributesToRemove.uuid) | |
591 | ||
592 | return db.sequelize.transaction(t => { | |
593 | return fetchVideoChannelByHostAndUUID(fromPod.host, videoChannelAttributesToRemove.uuid, t) | |
594 | .then(videoChannel => videoChannel.destroy({ transaction: t })) | |
595 | }) | |
596 | .then(() => logger.info('Remote video channel with uuid %s removed.', videoChannelAttributesToRemove.uuid)) | |
597 | .catch(err => { | |
598 | logger.debug('Cannot remove the remote video channel.', err) | |
599 | throw err | |
600 | }) | |
601 | } | |
602 | ||
603 | function reportAbuseRemoteVideoRetryWrapper (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) { | |
604 | const options = { | |
605 | arguments: [ reportData, fromPod ], | |
606 | errorMessage: 'Cannot create remote abuse video with many retries.' | |
607 | } | |
608 | ||
609 | return retryTransactionWrapper(reportAbuseRemoteVideo, options) | |
610 | } | |
611 | ||
4771e000 | 612 | function reportAbuseRemoteVideo (reportData: RemoteVideoReportAbuseData, fromPod: PodInstance) { |
72c7248b | 613 | logger.debug('Reporting remote abuse for video %s.', reportData.videoUUID) |
55fa55a9 | 614 | |
72c7248b C |
615 | return db.sequelize.transaction(t => { |
616 | return fetchVideoByUUID(reportData.videoUUID, t) | |
617 | .then(video => { | |
618 | const videoAbuseData = { | |
619 | reporterUsername: reportData.reporterUsername, | |
620 | reason: reportData.reportReason, | |
621 | reporterPodId: fromPod.id, | |
622 | videoId: video.id | |
623 | } | |
d8cc063e | 624 | |
72c7248b C |
625 | return db.VideoAbuse.create(videoAbuseData) |
626 | }) | |
627 | }) | |
628 | .then(() => logger.info('Remote abuse for video uuid %s created', reportData.videoUUID)) | |
629 | .catch(err => { | |
630 | // This is just a debug because we will retry the insert | |
631 | logger.debug('Cannot create remote abuse video', err) | |
632 | throw err | |
633 | }) | |
55fa55a9 C |
634 | } |
635 | ||
72c7248b C |
636 | function fetchVideoByUUID (id: string, t: Sequelize.Transaction) { |
637 | return db.Video.loadByUUID(id, t) | |
6fcd19ba C |
638 | .then(video => { |
639 | if (!video) throw new Error('Video not found') | |
e4c87ec2 | 640 | |
6fcd19ba C |
641 | return video |
642 | }) | |
643 | .catch(err => { | |
ad0997ad | 644 | logger.error('Cannot load owned video from id.', { error: err.stack, id }) |
6fcd19ba C |
645 | throw err |
646 | }) | |
e4c87ec2 C |
647 | } |
648 | ||
72c7248b C |
649 | function fetchVideoByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) { |
650 | return db.Video.loadByHostAndUUID(podHost, uuid, t) | |
6fcd19ba C |
651 | .then(video => { |
652 | if (!video) throw new Error('Video not found') | |
55fa55a9 | 653 | |
6fcd19ba C |
654 | return video |
655 | }) | |
656 | .catch(err => { | |
0a6658fd | 657 | logger.error('Cannot load video from host and uuid.', { error: err.stack, podHost, uuid }) |
6fcd19ba C |
658 | throw err |
659 | }) | |
528a9efa | 660 | } |
72c7248b C |
661 | |
662 | function fetchVideoChannelByHostAndUUID (podHost: string, uuid: string, t: Sequelize.Transaction) { | |
663 | return db.VideoChannel.loadByHostAndUUID(podHost, uuid, t) | |
664 | .then(videoChannel => { | |
665 | if (!videoChannel) throw new Error('Video channel not found') | |
666 | ||
667 | return videoChannel | |
668 | }) | |
669 | .catch(err => { | |
670 | logger.error('Cannot load video channel from host and uuid.', { error: err.stack, podHost, uuid }) | |
671 | throw err | |
672 | }) | |
673 | } |