aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/controllers/api/remote
diff options
context:
space:
mode:
Diffstat (limited to 'server/controllers/api/remote')
-rw-r--r--server/controllers/api/remote/index.js16
-rw-r--r--server/controllers/api/remote/videos.js328
2 files changed, 344 insertions, 0 deletions
diff --git a/server/controllers/api/remote/index.js b/server/controllers/api/remote/index.js
new file mode 100644
index 000000000..2947632d5
--- /dev/null
+++ b/server/controllers/api/remote/index.js
@@ -0,0 +1,16 @@
1'use strict'
2
3const express = require('express')
4
5const utils = require('../../../helpers/utils')
6
7const router = express.Router()
8
9const videosRemoteController = require('./videos')
10
11router.use('/videos', videosRemoteController)
12router.use('/*', utils.badRequest)
13
14// ---------------------------------------------------------------------------
15
16module.exports = router
diff --git a/server/controllers/api/remote/videos.js b/server/controllers/api/remote/videos.js
new file mode 100644
index 000000000..c45a86dbb
--- /dev/null
+++ b/server/controllers/api/remote/videos.js
@@ -0,0 +1,328 @@
1'use strict'
2
3const eachSeries = require('async/eachSeries')
4const express = require('express')
5const waterfall = require('async/waterfall')
6
7const db = require('../../../initializers/database')
8const middlewares = require('../../../middlewares')
9const secureMiddleware = middlewares.secure
10const videosValidators = middlewares.validators.remote.videos
11const signatureValidators = middlewares.validators.remote.signature
12const logger = require('../../../helpers/logger')
13const utils = require('../../../helpers/utils')
14
15const router = express.Router()
16
17router.post('/',
18 signatureValidators.signature,
19 secureMiddleware.checkSignature,
20 videosValidators.remoteVideos,
21 remoteVideos
22)
23
24// ---------------------------------------------------------------------------
25
26module.exports = router
27
28// ---------------------------------------------------------------------------
29
30function remoteVideos (req, res, next) {
31 const requests = req.body.data
32 const fromPod = res.locals.secure.pod
33
34 // We need to process in the same order to keep consistency
35 // TODO: optimization
36 eachSeries(requests, function (request, callbackEach) {
37 const data = request.data
38
39 switch (request.type) {
40 case 'add':
41 addRemoteVideoRetryWrapper(data, fromPod, callbackEach)
42 break
43
44 case 'update':
45 updateRemoteVideoRetryWrapper(data, fromPod, callbackEach)
46 break
47
48 case 'remove':
49 removeRemoteVideo(data, fromPod, callbackEach)
50 break
51
52 case 'report-abuse':
53 reportAbuseRemoteVideo(data, fromPod, callbackEach)
54 break
55
56 default:
57 logger.error('Unkown remote request type %s.', request.type)
58 }
59 }, function (err) {
60 if (err) logger.error('Error managing remote videos.', { error: err })
61 })
62
63 // We don't need to keep the other pod waiting
64 return res.type('json').status(204).end()
65}
66
67// Handle retries on fail
68function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) {
69 utils.transactionRetryer(
70 function (callback) {
71 return addRemoteVideo(videoToCreateData, fromPod, callback)
72 },
73 function (err) {
74 if (err) {
75 logger.error('Cannot insert the remote video with many retries.', { error: err })
76 }
77
78 // Do not return the error, continue the process
79 return finalCallback(null)
80 }
81 )
82}
83
84function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
85 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
86
87 waterfall([
88
89 function startTransaction (callback) {
90 db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) {
91 return callback(err, t)
92 })
93 },
94
95 function findOrCreateAuthor (t, callback) {
96 const name = videoToCreateData.author
97 const podId = fromPod.id
98 // This author is from another pod so we do not associate a user
99 const userId = null
100
101 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
102 return callback(err, t, authorInstance)
103 })
104 },
105
106 function findOrCreateTags (t, author, callback) {
107 const tags = videoToCreateData.tags
108
109 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
110 return callback(err, t, author, tagInstances)
111 })
112 },
113
114 function createVideoObject (t, author, tagInstances, callback) {
115 const videoData = {
116 name: videoToCreateData.name,
117 remoteId: videoToCreateData.remoteId,
118 extname: videoToCreateData.extname,
119 infoHash: videoToCreateData.infoHash,
120 description: videoToCreateData.description,
121 authorId: author.id,
122 duration: videoToCreateData.duration,
123 createdAt: videoToCreateData.createdAt,
124 // FIXME: updatedAt does not seems to be considered by Sequelize
125 updatedAt: videoToCreateData.updatedAt
126 }
127
128 const video = db.Video.build(videoData)
129
130 return callback(null, t, tagInstances, video)
131 },
132
133 function generateThumbnail (t, tagInstances, video, callback) {
134 db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
135 if (err) {
136 logger.error('Cannot generate thumbnail from data.', { error: err })
137 return callback(err)
138 }
139
140 return callback(err, t, tagInstances, video)
141 })
142 },
143
144 function insertVideoIntoDB (t, tagInstances, video, callback) {
145 const options = {
146 transaction: t
147 }
148
149 video.save(options).asCallback(function (err, videoCreated) {
150 return callback(err, t, tagInstances, videoCreated)
151 })
152 },
153
154 function associateTagsToVideo (t, tagInstances, video, callback) {
155 const options = { transaction: t }
156
157 video.setTags(tagInstances, options).asCallback(function (err) {
158 return callback(err, t)
159 })
160 }
161
162 ], function (err, t) {
163 if (err) {
164 // This is just a debug because we will retry the insert
165 logger.debug('Cannot insert the remote video.', { error: err })
166
167 // Abort transaction?
168 if (t) t.rollback()
169
170 return finalCallback(err)
171 }
172
173 // Commit transaction
174 t.commit().asCallback(function (err) {
175 if (err) return finalCallback(err)
176
177 logger.info('Remote video %s inserted.', videoToCreateData.name)
178 return finalCallback(null)
179 })
180 })
181}
182
183// Handle retries on fail
184function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) {
185 utils.transactionRetryer(
186 function (callback) {
187 return updateRemoteVideo(videoAttributesToUpdate, fromPod, callback)
188 },
189 function (err) {
190 if (err) {
191 logger.error('Cannot update the remote video with many retries.', { error: err })
192 }
193
194 // Do not return the error, continue the process
195 return finalCallback(null)
196 }
197 )
198}
199
200function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
201 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
202
203 waterfall([
204
205 function startTransaction (callback) {
206 db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) {
207 return callback(err, t)
208 })
209 },
210
211 function findVideo (t, callback) {
212 fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
213 return callback(err, t, videoInstance)
214 })
215 },
216
217 function findOrCreateTags (t, videoInstance, callback) {
218 const tags = videoAttributesToUpdate.tags
219
220 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
221 return callback(err, t, videoInstance, tagInstances)
222 })
223 },
224
225 function updateVideoIntoDB (t, videoInstance, tagInstances, callback) {
226 const options = { transaction: t }
227
228 videoInstance.set('name', videoAttributesToUpdate.name)
229 videoInstance.set('description', videoAttributesToUpdate.description)
230 videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
231 videoInstance.set('duration', videoAttributesToUpdate.duration)
232 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
233 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
234 videoInstance.set('extname', videoAttributesToUpdate.extname)
235
236 videoInstance.save(options).asCallback(function (err) {
237 return callback(err, t, videoInstance, tagInstances)
238 })
239 },
240
241 function associateTagsToVideo (t, videoInstance, tagInstances, callback) {
242 const options = { transaction: t }
243
244 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
245 return callback(err, t)
246 })
247 }
248
249 ], function (err, t) {
250 if (err) {
251 // This is just a debug because we will retry the insert
252 logger.debug('Cannot update the remote video.', { error: err })
253
254 // Abort transaction?
255 if (t) t.rollback()
256
257 return finalCallback(err)
258 }
259
260 // Commit transaction
261 t.commit().asCallback(function (err) {
262 if (err) return finalCallback(err)
263
264 logger.info('Remote video %s updated', videoAttributesToUpdate.name)
265 return finalCallback(null)
266 })
267 })
268}
269
270function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
271 // We need the instance because we have to remove some other stuffs (thumbnail etc)
272 fetchVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
273 // Do not return the error, continue the process
274 if (err) return callback(null)
275
276 logger.debug('Removing remote video %s.', video.remoteId)
277 video.destroy().asCallback(function (err) {
278 // Do not return the error, continue the process
279 if (err) {
280 logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err })
281 }
282
283 return callback(null)
284 })
285 })
286}
287
288function reportAbuseRemoteVideo (reportData, fromPod, callback) {
289 db.Video.load(reportData.videoRemoteId, function (err, video) {
290 if (err || !video) {
291 if (!err) err = new Error('video not found')
292
293 logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId })
294 // Do not return the error, continue the process
295 return callback(null)
296 }
297
298 logger.debug('Reporting remote abuse for video %s.', video.id)
299
300 const videoAbuseData = {
301 reporterUsername: reportData.reporterUsername,
302 reason: reportData.reportReason,
303 reporterPodId: fromPod.id,
304 videoId: video.id
305 }
306
307 db.VideoAbuse.create(videoAbuseData).asCallback(function (err) {
308 if (err) {
309 logger.error('Cannot create remote abuse video.', { error: err })
310 }
311
312 return callback(null)
313 })
314 })
315}
316
317function fetchVideo (podHost, remoteId, callback) {
318 db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) {
319 if (err || !video) {
320 if (!err) err = new Error('video not found')
321
322 logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId })
323 return callback(err)
324 }
325
326 return callback(null, video)
327 })
328}