]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/api/remote/videos.ts
Fix error logging
[github/Chocobozzz/PeerTube.git] / server / controllers / api / remote / videos.ts
CommitLineData
4d4e5cd4 1import * as express from 'express'
6fcd19ba 2import * as Promise from 'bluebird'
528a9efa 3
e02643f3 4import { database as db } from '../../../initializers/database'
65fcc311
C
5import {
6 REQUEST_ENDPOINT_ACTIONS,
7 REQUEST_ENDPOINTS,
8 REQUEST_VIDEO_EVENT_TYPES,
9 REQUEST_VIDEO_QADU_TYPES
10} from '../../../initializers'
11import {
12 checkSignature,
13 signatureValidator,
14 remoteVideosValidator,
15 remoteQaduVideosValidator,
16 remoteEventsVideosValidator
17} from '../../../middlewares'
6fcd19ba 18import { logger, retryTransactionWrapper } from '../../../helpers'
65fcc311 19import { quickAndDirtyUpdatesVideoToFriends } from '../../../lib'
69818c93 20import { PodInstance, VideoInstance } from '../../../models'
65fcc311
C
21
22const ENDPOINT_ACTIONS = REQUEST_ENDPOINT_ACTIONS[REQUEST_ENDPOINTS.VIDEOS]
62f4ef41
C
23
24// Functions to call when processing a remote request
6fcd19ba 25const functionsHash: { [ id: string ]: (...args) => Promise<any> } = {}
62f4ef41
C
26functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper
27functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper
28functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo
29functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideo
30
65fcc311 31const remoteVideosRouter = express.Router()
528a9efa 32
65fcc311
C
33remoteVideosRouter.post('/',
34 signatureValidator,
35 checkSignature,
36 remoteVideosValidator,
528a9efa
C
37 remoteVideos
38)
39
65fcc311
C
40remoteVideosRouter.post('/qadu',
41 signatureValidator,
42 checkSignature,
43 remoteQaduVideosValidator,
9e167724
C
44 remoteVideosQadu
45)
46
65fcc311
C
47remoteVideosRouter.post('/events',
48 signatureValidator,
49 checkSignature,
50 remoteEventsVideosValidator,
e4c87ec2
C
51 remoteVideosEvents
52)
53
528a9efa
C
54// ---------------------------------------------------------------------------
55
65fcc311
C
56export {
57 remoteVideosRouter
58}
528a9efa
C
59
60// ---------------------------------------------------------------------------
61
69818c93 62function remoteVideos (req: express.Request, res: express.Response, next: express.NextFunction) {
528a9efa 63 const requests = req.body.data
4ff0d862 64 const fromPod = res.locals.secure.pod
528a9efa
C
65
66 // We need to process in the same order to keep consistency
709756b8 67 Promise.each(requests, (request: any) => {
55fa55a9 68 const data = request.data
528a9efa 69
62f4ef41
C
70 // Get the function we need to call in order to process the request
71 const fun = functionsHash[request.type]
72 if (fun === undefined) {
73 logger.error('Unkown remote request type %s.', request.type)
6fcd19ba 74 return
528a9efa 75 }
62f4ef41 76
6fcd19ba 77 return fun.call(this, data, fromPod)
528a9efa 78 })
ad0997ad 79 .catch(err => logger.error('Error managing remote videos.', err))
528a9efa 80
709756b8 81 // Don't block the other pod
528a9efa
C
82 return res.type('json').status(204).end()
83}
84
69818c93 85function remoteVideosQadu (req: express.Request, res: express.Response, next: express.NextFunction) {
9e167724
C
86 const requests = req.body.data
87 const fromPod = res.locals.secure.pod
88
709756b8 89 Promise.each(requests, (request: any) => {
9e167724
C
90 const videoData = request.data
91
6fcd19ba 92 return quickAndDirtyUpdateVideoRetryWrapper(videoData, fromPod)
9e167724 93 })
ad0997ad 94 .catch(err => logger.error('Error managing remote videos.', err))
9e167724
C
95
96 return res.type('json').status(204).end()
97}
98
69818c93 99function remoteVideosEvents (req: express.Request, res: express.Response, next: express.NextFunction) {
e4c87ec2
C
100 const requests = req.body.data
101 const fromPod = res.locals.secure.pod
102
709756b8 103 Promise.each(requests, (request: any) => {
e4c87ec2
C
104 const eventData = request.data
105
6fcd19ba 106 return processVideosEventsRetryWrapper(eventData, fromPod)
e4c87ec2 107 })
ad0997ad 108 .catch(err => logger.error('Error managing remote videos.', err))
e4c87ec2
C
109
110 return res.type('json').status(204).end()
111}
112
6fcd19ba 113function processVideosEventsRetryWrapper (eventData: any, fromPod: PodInstance) {
e4c87ec2
C
114 const options = {
115 arguments: [ eventData, fromPod ],
116 errorMessage: 'Cannot process videos events with many retries.'
117 }
118
6fcd19ba 119 return retryTransactionWrapper(processVideosEvents, options)
e4c87ec2
C
120}
121
6fcd19ba 122function processVideosEvents (eventData: any, fromPod: PodInstance) {
e4c87ec2 123
6fcd19ba
C
124 return db.sequelize.transaction(t => {
125 return fetchOwnedVideo(eventData.remoteId)
126 .then(videoInstance => {
127 const options = { transaction: t }
e4c87ec2 128
6fcd19ba
C
129 let columnToUpdate
130 let qaduType
e4c87ec2 131
6fcd19ba
C
132 switch (eventData.eventType) {
133 case REQUEST_VIDEO_EVENT_TYPES.VIEWS:
134 columnToUpdate = 'views'
135 qaduType = REQUEST_VIDEO_QADU_TYPES.VIEWS
136 break
e4c87ec2 137
6fcd19ba
C
138 case REQUEST_VIDEO_EVENT_TYPES.LIKES:
139 columnToUpdate = 'likes'
140 qaduType = REQUEST_VIDEO_QADU_TYPES.LIKES
141 break
e4c87ec2 142
6fcd19ba
C
143 case REQUEST_VIDEO_EVENT_TYPES.DISLIKES:
144 columnToUpdate = 'dislikes'
145 qaduType = REQUEST_VIDEO_QADU_TYPES.DISLIKES
146 break
e4c87ec2 147
6fcd19ba
C
148 default:
149 throw new Error('Unknown video event type.')
150 }
e4c87ec2 151
6fcd19ba
C
152 const query = {}
153 query[columnToUpdate] = eventData.count
e4c87ec2 154
6fcd19ba 155 return videoInstance.increment(query, options).then(() => ({ videoInstance, qaduType }))
d38b8281 156 })
6fcd19ba
C
157 .then(({ videoInstance, qaduType }) => {
158 const qadusParams = [
159 {
160 videoId: videoInstance.id,
161 type: qaduType
162 }
163 ]
164
165 return quickAndDirtyUpdatesVideoToFriends(qadusParams, t)
e4c87ec2 166 })
6fcd19ba
C
167 })
168 .then(() => logger.info('Remote video event processed for video %s.', eventData.remoteId))
169 .catch(err => {
ad0997ad 170 logger.debug('Cannot process a video event.', err)
6fcd19ba 171 throw err
e4c87ec2
C
172 })
173}
174
6fcd19ba 175function quickAndDirtyUpdateVideoRetryWrapper (videoData: any, fromPod: PodInstance) {
9e167724
C
176 const options = {
177 arguments: [ videoData, fromPod ],
178 errorMessage: 'Cannot update quick and dirty the remote video with many retries.'
179 }
180
6fcd19ba 181 return retryTransactionWrapper(quickAndDirtyUpdateVideo, options)
9e167724
C
182}
183
6fcd19ba 184function quickAndDirtyUpdateVideo (videoData: any, fromPod: PodInstance) {
f148e5ed
C
185 let videoName
186
6fcd19ba
C
187 return db.sequelize.transaction(t => {
188 return fetchRemoteVideo(fromPod.host, videoData.remoteId)
189 .then(videoInstance => {
190 const options = { transaction: t }
9e167724 191
6fcd19ba 192 videoName = videoInstance.name
f148e5ed 193
6fcd19ba
C
194 if (videoData.views) {
195 videoInstance.set('views', videoData.views)
196 }
9e167724 197
6fcd19ba
C
198 if (videoData.likes) {
199 videoInstance.set('likes', videoData.likes)
200 }
9e167724 201
6fcd19ba
C
202 if (videoData.dislikes) {
203 videoInstance.set('dislikes', videoData.dislikes)
204 }
9e167724 205
6fcd19ba 206 return videoInstance.save(options)
9e167724 207 })
9e167724 208 })
6fcd19ba 209 .then(() => logger.info('Remote video %s quick and dirty updated', videoName))
ad0997ad 210 .catch(err => logger.debug('Cannot quick and dirty update the remote video.', err))
9e167724
C
211}
212
ed04d94f 213// Handle retries on fail
6fcd19ba 214function addRemoteVideoRetryWrapper (videoToCreateData: any, fromPod: PodInstance) {
d6a5b018
C
215 const options = {
216 arguments: [ videoToCreateData, fromPod ],
217 errorMessage: 'Cannot insert the remote video with many retries.'
218 }
ed04d94f 219
6fcd19ba 220 return retryTransactionWrapper(addRemoteVideo, options)
ed04d94f
C
221}
222
6fcd19ba 223function addRemoteVideo (videoToCreateData: any, fromPod: PodInstance) {
ed04d94f 224 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
6666aad4 225
6fcd19ba
C
226 return db.sequelize.transaction(t => {
227 return db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId)
228 .then(video => {
229 if (video) throw new Error('RemoteId and host pair is not unique.')
cddadde8 230
6fcd19ba 231 return undefined
cddadde8 232 })
6fcd19ba
C
233 .then(() => {
234 const name = videoToCreateData.author
235 const podId = fromPod.id
236 // This author is from another pod so we do not associate a user
237 const userId = null
feb4bdfd 238
6fcd19ba 239 return db.Author.findOrCreateAuthor(name, podId, userId, t)
feb4bdfd 240 })
6fcd19ba
C
241 .then(author => {
242 const tags = videoToCreateData.tags
feb4bdfd 243
6fcd19ba 244 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ author, tagInstances }))
7920c273 245 })
6fcd19ba
C
246 .then(({ author, tagInstances }) => {
247 const videoData = {
248 name: videoToCreateData.name,
249 remoteId: videoToCreateData.remoteId,
250 extname: videoToCreateData.extname,
251 infoHash: videoToCreateData.infoHash,
252 category: videoToCreateData.category,
253 licence: videoToCreateData.licence,
254 language: videoToCreateData.language,
255 nsfw: videoToCreateData.nsfw,
256 description: videoToCreateData.description,
257 authorId: author.id,
258 duration: videoToCreateData.duration,
259 createdAt: videoToCreateData.createdAt,
260 // FIXME: updatedAt does not seems to be considered by Sequelize
261 updatedAt: videoToCreateData.updatedAt,
262 views: videoToCreateData.views,
263 likes: videoToCreateData.likes,
264 dislikes: videoToCreateData.dislikes
feb4bdfd
C
265 }
266
6fcd19ba
C
267 const video = db.Video.build(videoData)
268 return { tagInstances, video }
feb4bdfd 269 })
6fcd19ba
C
270 .then(({ tagInstances, video }) => {
271 return db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData).then(() => ({ tagInstances, video }))
7920c273 272 })
6fcd19ba
C
273 .then(({ tagInstances, video }) => {
274 const options = {
275 transaction: t
276 }
7920c273 277
6fcd19ba 278 return video.save(options).then(videoCreated => ({ tagInstances, videoCreated }))
7920c273 279 })
6fcd19ba
C
280 .then(({ tagInstances, videoCreated }) => {
281 const options = {
282 transaction: t
283 }
7920c273 284
6fcd19ba
C
285 return videoCreated.setTags(tagInstances, options)
286 })
287 })
288 .then(() => logger.info('Remote video %s inserted.', videoToCreateData.name))
289 .catch(err => {
ad0997ad 290 logger.debug('Cannot insert the remote video.', err)
6fcd19ba 291 throw err
7920c273 292 })
528a9efa
C
293}
294
ed04d94f 295// Handle retries on fail
6fcd19ba 296function updateRemoteVideoRetryWrapper (videoAttributesToUpdate: any, fromPod: PodInstance) {
d6a5b018 297 const options = {
fbc22d79 298 arguments: [ videoAttributesToUpdate, fromPod ],
d6a5b018
C
299 errorMessage: 'Cannot update the remote video with many retries'
300 }
ed04d94f 301
6fcd19ba 302 return retryTransactionWrapper(updateRemoteVideo, options)
ed04d94f
C
303}
304
6fcd19ba 305function updateRemoteVideo (videoAttributesToUpdate: any, fromPod: PodInstance) {
ed04d94f 306 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
feb4bdfd 307
6fcd19ba
C
308 return db.sequelize.transaction(t => {
309 return fetchRemoteVideo(fromPod.host, videoAttributesToUpdate.remoteId)
310 .then(videoInstance => {
311 const tags = videoAttributesToUpdate.tags
3d118fb5 312
6fcd19ba 313 return db.Tag.findOrCreateTags(tags, t).then(tagInstances => ({ videoInstance, tagInstances }))
3d118fb5 314 })
6fcd19ba
C
315 .then(({ videoInstance, tagInstances }) => {
316 const options = { transaction: t }
317
318 videoInstance.set('name', videoAttributesToUpdate.name)
319 videoInstance.set('category', videoAttributesToUpdate.category)
320 videoInstance.set('licence', videoAttributesToUpdate.licence)
321 videoInstance.set('language', videoAttributesToUpdate.language)
322 videoInstance.set('nsfw', videoAttributesToUpdate.nsfw)
323 videoInstance.set('description', videoAttributesToUpdate.description)
324 videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
325 videoInstance.set('duration', videoAttributesToUpdate.duration)
326 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
327 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
328 videoInstance.set('extname', videoAttributesToUpdate.extname)
329 videoInstance.set('views', videoAttributesToUpdate.views)
330 videoInstance.set('likes', videoAttributesToUpdate.likes)
331 videoInstance.set('dislikes', videoAttributesToUpdate.dislikes)
332
333 return videoInstance.save(options).then(() => ({ videoInstance, tagInstances }))
3d118fb5 334 })
6fcd19ba
C
335 .then(({ videoInstance, tagInstances }) => {
336 const options = { transaction: t }
3d118fb5 337
6fcd19ba 338 return videoInstance.setTags(tagInstances, options)
3d118fb5 339 })
6fcd19ba
C
340 })
341 .then(() => logger.info('Remote video %s updated', videoAttributesToUpdate.name))
342 .catch(err => {
343 // This is just a debug because we will retry the insert
ad0997ad 344 logger.debug('Cannot update the remote video.', err)
6fcd19ba 345 throw err
3d118fb5
C
346 })
347}
348
6fcd19ba 349function removeRemoteVideo (videoToRemoveData: any, fromPod: PodInstance) {
3d118fb5 350 // We need the instance because we have to remove some other stuffs (thumbnail etc)
6fcd19ba
C
351 return fetchRemoteVideo(fromPod.host, videoToRemoveData.remoteId)
352 .then(video => {
353 logger.debug('Removing remote video %s.', video.remoteId)
354 return video.destroy()
355 })
356 .catch(err => {
ad0997ad 357 logger.debug('Could not fetch remote video.', { host: fromPod.host, remoteId: videoToRemoveData.remoteId, error: err.stack })
d8cc063e 358 })
55fa55a9
C
359}
360
6fcd19ba
C
361function reportAbuseRemoteVideo (reportData: any, fromPod: PodInstance) {
362 return fetchOwnedVideo(reportData.videoRemoteId)
363 .then(video => {
364 logger.debug('Reporting remote abuse for video %s.', video.id)
55fa55a9 365
6fcd19ba
C
366 const videoAbuseData = {
367 reporterUsername: reportData.reporterUsername,
368 reason: reportData.reportReason,
369 reporterPodId: fromPod.id,
370 videoId: video.id
d8cc063e
C
371 }
372
6fcd19ba 373 return db.VideoAbuse.create(videoAbuseData)
d8cc063e 374 })
ad0997ad 375 .catch(err => logger.error('Cannot create remote abuse video.', err))
55fa55a9
C
376}
377
6fcd19ba
C
378function fetchOwnedVideo (id: string) {
379 return db.Video.load(id)
380 .then(video => {
381 if (!video) throw new Error('Video not found')
e4c87ec2 382
6fcd19ba
C
383 return video
384 })
385 .catch(err => {
ad0997ad 386 logger.error('Cannot load owned video from id.', { error: err.stack, id })
6fcd19ba
C
387 throw err
388 })
e4c87ec2
C
389}
390
6fcd19ba
C
391function fetchRemoteVideo (podHost: string, remoteId: string) {
392 return db.Video.loadByHostAndRemoteId(podHost, remoteId)
393 .then(video => {
394 if (!video) throw new Error('Video not found')
55fa55a9 395
6fcd19ba
C
396 return video
397 })
398 .catch(err => {
ad0997ad 399 logger.error('Cannot load video from host and remote id.', { error: err.stack, podHost, remoteId })
6fcd19ba
C
400 throw err
401 })
528a9efa 402}