Server: fix video remoe validation
[oweals/peertube.git] / server / controllers / api / remote.js
1 'use strict'
2
3 const eachSeries = require('async/eachSeries')
4 const express = require('express')
5 const waterfall = require('async/waterfall')
6
7 const db = require('../../initializers/database')
8 const middlewares = require('../../middlewares')
9 const secureMiddleware = middlewares.secure
10 const validators = middlewares.validators.remote
11 const logger = require('../../helpers/logger')
12
13 const router = express.Router()
14
15 router.post('/videos',
16   validators.signature,
17   secureMiddleware.checkSignature,
18   validators.remoteVideos,
19   remoteVideos
20 )
21
22 // ---------------------------------------------------------------------------
23
24 module.exports = router
25
26 // ---------------------------------------------------------------------------
27
28 function remoteVideos (req, res, next) {
29   const requests = req.body.data
30   const fromPod = res.locals.secure.pod
31
32   // We need to process in the same order to keep consistency
33   // TODO: optimization
34   eachSeries(requests, function (request, callbackEach) {
35     const videoData = request.data
36
37     switch (request.type) {
38       case 'add':
39         addRemoteVideo(videoData, fromPod, callbackEach)
40         break
41
42       case 'update':
43         updateRemoteVideo(videoData, fromPod, callbackEach)
44         break
45
46       case 'remove':
47         removeRemoteVideo(videoData, fromPod, callbackEach)
48         break
49
50       default:
51         logger.error('Unkown remote request type %s.', request.type)
52     }
53   }, function (err) {
54     if (err) logger.error('Error managing remote videos.', { error: err })
55   })
56
57   // We don't need to keep the other pod waiting
58   return res.type('json').status(204).end()
59 }
60
61 function addRemoteVideo (videoToCreateData, fromPod, finalCallback) {
62   logger.debug('Adding remote video "%s".', videoToCreateData.name)
63
64   waterfall([
65
66     function startTransaction (callback) {
67       db.sequelize.transaction().asCallback(function (err, t) {
68         return callback(err, t)
69       })
70     },
71
72     function findOrCreateAuthor (t, callback) {
73       const name = videoToCreateData.author
74       const podId = fromPod.id
75       // This author is from another pod so we do not associate a user
76       const userId = null
77
78       db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) {
79         return callback(err, t, authorInstance)
80       })
81     },
82
83     function findOrCreateTags (t, author, callback) {
84       const tags = videoToCreateData.tags
85
86       db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
87         return callback(err, t, author, tagInstances)
88       })
89     },
90
91     function createVideoObject (t, author, tagInstances, callback) {
92       const videoData = {
93         name: videoToCreateData.name,
94         remoteId: videoToCreateData.remoteId,
95         extname: videoToCreateData.extname,
96         infoHash: videoToCreateData.infoHash,
97         description: videoToCreateData.description,
98         authorId: author.id,
99         duration: videoToCreateData.duration,
100         createdAt: videoToCreateData.createdAt,
101         updatedAt: videoToCreateData.updatedAt
102       }
103
104       const video = db.Video.build(videoData)
105
106       return callback(null, t, tagInstances, video)
107     },
108
109     function generateThumbnail (t, tagInstances, video, callback) {
110       db.Video.generateThumbnailFromData(video, videoToCreateData.thumbnailData, function (err) {
111         if (err) {
112           logger.error('Cannot generate thumbnail from data.', { error: err })
113           return callback(err)
114         }
115
116         return callback(err, t, tagInstances, video)
117       })
118     },
119
120     function insertVideoIntoDB (t, tagInstances, video, callback) {
121       const options = {
122         transaction: t
123       }
124
125       video.save(options).asCallback(function (err, videoCreated) {
126         return callback(err, t, tagInstances, videoCreated)
127       })
128     },
129
130     function associateTagsToVideo (t, tagInstances, video, callback) {
131       const options = { transaction: t }
132
133       video.setTags(tagInstances, options).asCallback(function (err) {
134         return callback(err, t)
135       })
136     }
137
138   ], function (err, t) {
139     if (err) {
140       logger.error('Cannot insert the remote video.')
141
142       // Abort transaction?
143       if (t) t.rollback()
144
145       return finalCallback(err)
146     }
147
148     // Commit transaction
149     t.commit()
150
151     return finalCallback()
152   })
153 }
154
155 function updateRemoteVideo (videoAttributesToUpdate, fromPod, finalCallback) {
156   logger.debug('Updating remote video "%s".', videoAttributesToUpdate.name)
157
158   waterfall([
159
160     function startTransaction (callback) {
161       db.sequelize.transaction().asCallback(function (err, t) {
162         return callback(err, t)
163       })
164     },
165
166     function findVideo (t, callback) {
167       db.Video.loadByHostAndRemoteId(fromPod.host, videoAttributesToUpdate.remoteId, function (err, videoInstance) {
168         if (err || !videoInstance) {
169           logger.error('Cannot load video from host and remote id.', { error: err.message })
170           return callback(err)
171         }
172
173         return callback(null, t, videoInstance)
174       })
175     },
176
177     function findOrCreateTags (t, videoInstance, callback) {
178       const tags = videoAttributesToUpdate.tags
179
180       db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) {
181         return callback(err, t, videoInstance, tagInstances)
182       })
183     },
184
185     function updateVideoIntoDB (t, videoInstance, tagInstances, callback) {
186       const options = { transaction: t }
187
188       videoInstance.set('name', videoAttributesToUpdate.name)
189       videoInstance.set('description', videoAttributesToUpdate.description)
190       videoInstance.set('infoHash', videoAttributesToUpdate.infoHash)
191       videoInstance.set('duration', videoAttributesToUpdate.duration)
192       videoInstance.set('createdAt', videoAttributesToUpdate.createdAt)
193       videoInstance.set('updatedAt', videoAttributesToUpdate.updatedAt)
194       videoInstance.set('extname', videoAttributesToUpdate.extname)
195
196       videoInstance.save(options).asCallback(function (err) {
197         return callback(err, t, videoInstance, tagInstances)
198       })
199     },
200
201     function associateTagsToVideo (t, videoInstance, tagInstances, callback) {
202       const options = { transaction: t }
203
204       videoInstance.setTags(tagInstances, options).asCallback(function (err) {
205         return callback(err, t)
206       })
207     }
208
209   ], function (err, t) {
210     if (err) {
211       logger.error('Cannot update the remote video.')
212
213       // Abort transaction?
214       if (t) t.rollback()
215
216       return finalCallback(err)
217     }
218
219     // Commit transaction
220     t.commit()
221
222     return finalCallback()
223   })
224 }
225
226 function removeRemoteVideo (videoToRemoveData, fromPod, callback) {
227   // We need the instance because we have to remove some other stuffs (thumbnail etc)
228   db.Video.loadByHostAndRemoteId(fromPod.host, videoToRemoveData.remoteId, function (err, video) {
229     if (err || !video) {
230       logger.error('Cannot load video from host and remote id.', { error: err.message })
231       return callback(err)
232     }
233
234     logger.debug('Removing remote video %s.', video.remoteId)
235     video.destroy().asCallback(callback)
236   })
237 }