]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame - server/controllers/api/remote/videos.js
Server: assert remoteId and host pair is unique
[github/Chocobozzz/PeerTube.git] / server / controllers / api / remote / videos.js
CommitLineData
528a9efa
C
1'use strict'
2
1a42c9e2 3const eachSeries = require('async/eachSeries')
528a9efa 4const express = require('express')
feb4bdfd 5const waterfall = require('async/waterfall')
528a9efa 6
a6fd2b30 7const db = require('../../../initializers/database')
62f4ef41 8const constants = require('../../../initializers/constants')
a6fd2b30 9const middlewares = require('../../../middlewares')
528a9efa 10const secureMiddleware = middlewares.secure
55fa55a9
C
11const videosValidators = middlewares.validators.remote.videos
12const signatureValidators = middlewares.validators.remote.signature
a6fd2b30 13const logger = require('../../../helpers/logger')
4df023f2 14const databaseUtils = require('../../../helpers/database-utils')
528a9efa 15
62f4ef41
C
16const ENDPOINT_ACTIONS = constants.REQUEST_ENDPOINT_ACTIONS[constants.REQUEST_ENDPOINTS.VIDEOS]
17
18// Functions to call when processing a remote request
19const functionsHash = {}
20functionsHash[ENDPOINT_ACTIONS.ADD] = addRemoteVideoRetryWrapper
21functionsHash[ENDPOINT_ACTIONS.UPDATE] = updateRemoteVideoRetryWrapper
22functionsHash[ENDPOINT_ACTIONS.REMOVE] = removeRemoteVideo
23functionsHash[ENDPOINT_ACTIONS.REPORT_ABUSE] = reportAbuseRemoteVideo
24
528a9efa
C
25const router = express.Router()
26
a6fd2b30 27router.post('/',
55fa55a9 28 signatureValidators.signature,
0eb78d53 29 secureMiddleware.checkSignature,
55fa55a9 30 videosValidators.remoteVideos,
528a9efa
C
31 remoteVideos
32)
33
34// ---------------------------------------------------------------------------
35
36module.exports = router
37
38// ---------------------------------------------------------------------------
39
40function remoteVideos (req, res, next) {
41 const requests = req.body.data
4ff0d862 42 const fromPod = res.locals.secure.pod
528a9efa
C
43
44 // We need to process in the same order to keep consistency
45 // TODO: optimization
1a42c9e2 46 eachSeries(requests, function (request, callbackEach) {
55fa55a9 47 const data = request.data
528a9efa 48
62f4ef41
C
49 // Get the function we need to call in order to process the request
50 const fun = functionsHash[request.type]
51 if (fun === undefined) {
52 logger.error('Unkown remote request type %s.', request.type)
53 return callbackEach(null)
528a9efa 54 }
62f4ef41
C
55
56 fun.call(this, data, fromPod, callbackEach)
aaf61f38
C
57 }, function (err) {
58 if (err) logger.error('Error managing remote videos.', { error: err })
528a9efa
C
59 })
60
61 // We don't need to keep the other pod waiting
62 return res.type('json').status(204).end()
63}
64
ed04d94f
C
65// Handle retries on fail
66function addRemoteVideoRetryWrapper (videoToCreateData, fromPod, finalCallback) {
d6a5b018
C
67 const options = {
68 arguments: [ videoToCreateData, fromPod ],
69 errorMessage: 'Cannot insert the remote video with many retries.'
70 }
ed04d94f 71
4df023f2 72 databaseUtils.retryTransactionWrapper(addRemoteVideo, options, finalCallback)
ed04d94f
C
73}
74
4ff0d862 75function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
ed04d94f 76 logger.debug('Adding remote video "%s".', videoToCreateData.remoteId)
6666aad4 77
feb4bdfd
C
78 waterfall([
79
4df023f2 80 databaseUtils.startSerializableTransaction,
7920c273 81
cddadde8
C
82 function assertRemoteIdAndHostUnique (t, callback) {
83 db.Video.loadByHostAndRemoteId(fromPod.host, videoToCreateData.remoteId, function (err, video) {
84 if (err) return callback(err)
85
86 if (video) return callback(new Error('RemoteId and host pair is not unique.'))
87
88 return callback(null, t)
89 })
90 },
91
4ff0d862
C
92 function findOrCreateAuthor (t, callback) {
93 const name = videoToCreateData.author
94 const podId = fromPod.id
95 // This author is from another pod so we do not associate a user
96 const userId = null
feb4bdfd 97
4ff0d862
C
98 db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
99 return callback(err, t, authorInstance)
feb4bdfd
C
100 })
101 },
102
7920c273
C
103 function findOrCreateTags (t, author, callback) {
104 const tags = videoToCreateData.tags
7920c273 105
4ff0d862 106 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
7920c273
C
107 return callback(err, t, author, tagInstances)
108 })
109 },
110
111 function createVideoObject (t, author, tagInstances, callback) {
feb4bdfd
C
112 const videoData = {
113 name: videoToCreateData.name,
114 remoteId: videoToCreateData.remoteId,
115 extname: videoToCreateData.extname,
116 infoHash: videoToCreateData.infoHash,
117 description: videoToCreateData.description,
118 authorId: author.id,
124648d7 119 duration: videoToCreateData.duration,
79066fdf 120 createdAt: videoToCreateData.createdAt,
ed04d94f 121 // FIXME: updatedAt does not seems to be considered by Sequelize
79066fdf 122 updatedAt: videoToCreateData.updatedAt
feb4bdfd
C
123 }
124
125 const video = db.Video.build(videoData)
126
7920c273 127 return callback(null, t, tagInstances, video)
feb4bdfd
C
128 },
129
7920c273 130 function generateThumbnail (t, tagInstances, video, callback) {
4d324488 131 db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
feb4bdfd 132 if (err) {
4d324488 133 logger.error('Cannot generate thumbnail from data.', { error: err })
feb4bdfd
C
134 return callback(err)
135 }
136
7920c273 137 return callback(err, t, tagInstances, video)
feb4bdfd
C
138 })
139 },
140
7920c273
C
141 function insertVideoIntoDB (t, tagInstances, video, callback) {
142 const options = {
143 transaction: t
144 }
145
146 video.save(options).asCallback(function (err, videoCreated) {
147 return callback(err, t, tagInstances, videoCreated)
148 })
149 },
150
151 function associateTagsToVideo (t, tagInstances, video, callback) {
62f4ef41
C
152 const options = {
153 transaction: t
154 }
7920c273
C
155
156 video.setTags(tagInstances, options).asCallback(function (err) {
157 return callback(err, t)
158 })
4145c1c6
C
159 },
160
161 databaseUtils.commitTransaction
c77fa067 162
7920c273
C
163 ], function (err, t) {
164 if (err) {
ed04d94f
C
165 // This is just a debug because we will retry the insert
166 logger.debug('Cannot insert the remote video.', { error: err })
4145c1c6 167 return databaseUtils.rollbackTransaction(err, t, finalCallback)
7920c273
C
168 }
169
4145c1c6
C
170 logger.info('Remote video %s inserted.', videoToCreateData.name)
171 return finalCallback(null)
7920c273 172 })
528a9efa
C
173}
174
ed04d94f
C
175// Handle retries on fail
176function updateRemoteVideoRetryWrapper (videoAttributesToUpdate, fromPod, finalCallback) {
d6a5b018 177 const options = {
fbc22d79 178 arguments: [ videoAttributesToUpdate, fromPod ],
d6a5b018
C
179 errorMessage: 'Cannot update the remote video with many retries'
180 }
ed04d94f 181
fbc22d79 182 databaseUtils.retryTransactionWrapper(updateRemoteVideo, options, finalCallback)
ed04d94f
C
183}
184
3d118fb5 185function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
ed04d94f 186 logger.debug('Updating remote video "%s".', videoAttributesToUpdate.remoteId)
feb4bdfd 187
3d118fb5
C
188 waterfall([
189
4df023f2 190 databaseUtils.startSerializableTransaction,
3d118fb5
C
191
192 function findVideo (t, callback) {
55fa55a9
C
193 fetchVideo(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
194 return callback(err, t, videoInstance)
3d118fb5
C
195 })
196 },
197
198 function findOrCreateTags (t, videoInstance, callback) {
199 const tags = videoAttributesToUpdate.tags
200
201 db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
202 return callback(err, t, videoInstance, tagInstances)
203 })
204 },
205
206 function updateVideoIntoDB (t, videoInstance, tagInstances, callback) {
207 const options = { transaction: t }
208
209 videoInstance.set('name', videoAttributesToUpdate.name)
210 videoInstance.set('description', videoAttributesToUpdate.description)
211 videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
212 videoInstance.set('duration', videoAttributesToUpdate.duration)
213 videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
79066fdf 214 videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
3d118fb5
C
215 videoInstance.set('extname', videoAttributesToUpdate.extname)
216
217 videoInstance.save(options).asCallback(function (err) {
218 return callback(err, t, videoInstance, tagInstances)
219 })
220 },
221
222 function associateTagsToVideo (t, videoInstance, tagInstances, callback) {
223 const options = { transaction: t }
224
225 videoInstance.setTags(tagInstances, options).asCallback(function (err) {
226 return callback(err, t)
227 })
4145c1c6
C
228 },
229
230 databaseUtils.commitTransaction
528a9efa 231
3d118fb5
C
232 ], function (err, t) {
233 if (err) {
ed04d94f
C
234 // This is just a debug because we will retry the insert
235 logger.debug('Cannot update the remote video.', { error: err })
4145c1c6 236 return databaseUtils.rollbackTransaction(err, t, finalCallback)
6666aad4
C
237 }
238
4145c1c6
C
239 logger.info('Remote video %s updated', videoAttributesToUpdate.name)
240 return finalCallback(null)
3d118fb5
C
241 })
242}
243
244function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
245 // We need the instance because we have to remove some other stuffs (thumbnail etc)
55fa55a9 246 fetchVideo(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
d8cc063e
C
247 // Do not return the error, continue the process
248 if (err) return callback(null)
55fa55a9
C
249
250 logger.debug('Removing remote video %s.', video.remoteId)
d8cc063e
C
251 video.destroy().asCallback(function (err) {
252 // Do not return the error, continue the process
253 if (err) {
254 logger.error('Cannot remove remote video with id %s.', videoToRemoveData.remoteId, { error: err })
255 }
256
257 return callback(null)
258 })
55fa55a9
C
259 })
260}
261
262function reportAbuseRemoteVideo (reportData, fromPod, callback) {
263 db.Video.load(reportData.videoRemoteId, function (err, video) {
3d118fb5 264 if (err || !video) {
55fa55a9
C
265 if (!err) err = new Error('video not found')
266
ed04d94f 267 logger.error('Cannot load video from id.', { error: err, id: reportData.videoRemoteId })
d8cc063e
C
268 // Do not return the error, continue the process
269 return callback(null)
3d118fb5 270 }
6666aad4 271
55fa55a9
C
272 logger.debug('Reporting remote abuse for video %s.', video.id)
273
274 const videoAbuseData = {
275 reporterUsername: reportData.reporterUsername,
276 reason: reportData.reportReason,
277 reporterPodId: fromPod.id,
278 videoId: video.id
279 }
280
d8cc063e
C
281 db.VideoAbuse.create(videoAbuseData).asCallback(function (err) {
282 if (err) {
283 logger.error('Cannot create remote abuse video.', { error: err })
284 }
285
286 return callback(null)
287 })
55fa55a9
C
288 })
289}
290
291function fetchVideo (podHost, remoteId, callback) {
292 db.Video.loadByHostAndRemoteId(podHost, remoteId, function (err, video) {
293 if (err || !video) {
294 if (!err) err = new Error('video not found')
295
ed04d94f 296 logger.error('Cannot load video from host and remote id.', { error: err, podHost, remoteId })
55fa55a9
C
297 return callback(err)
298 }
299
300 return callback(null, video)
528a9efa
C
301 })
302}