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