diff options
Diffstat (limited to 'server')
80 files changed, 4783 insertions, 2298 deletions
diff --git a/server/controllers/api/clients.js b/server/controllers/api/clients.js index 7755f6c2b..cf83cb835 100644 --- a/server/controllers/api/clients.js +++ b/server/controllers/api/clients.js | |||
@@ -1,13 +1,11 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | 4 | ||
6 | const constants = require('../../initializers/constants') | 5 | const constants = require('../../initializers/constants') |
6 | const db = require('../../initializers/database') | ||
7 | const logger = require('../../helpers/logger') | 7 | const logger = require('../../helpers/logger') |
8 | 8 | ||
9 | const Client = mongoose.model('OAuthClient') | ||
10 | |||
11 | const router = express.Router() | 9 | const router = express.Router() |
12 | 10 | ||
13 | router.get('/local', getLocalClient) | 11 | router.get('/local', getLocalClient) |
@@ -27,12 +25,12 @@ function getLocalClient (req, res, next) { | |||
27 | return res.type('json').status(403).end() | 25 | return res.type('json').status(403).end() |
28 | } | 26 | } |
29 | 27 | ||
30 | Client.loadFirstClient(function (err, client) { | 28 | db.OAuthClient.loadFirstClient(function (err, client) { |
31 | if (err) return next(err) | 29 | if (err) return next(err) |
32 | if (!client) return next(new Error('No client available.')) | 30 | if (!client) return next(new Error('No client available.')) |
33 | 31 | ||
34 | res.json({ | 32 | res.json({ |
35 | client_id: client._id, | 33 | client_id: client.clientId, |
36 | client_secret: client.clientSecret | 34 | client_secret: client.clientSecret |
37 | }) | 35 | }) |
38 | }) | 36 | }) |
diff --git a/server/controllers/api/index.js b/server/controllers/api/index.js index 4cb65ed55..f13ff922c 100644 --- a/server/controllers/api/index.js +++ b/server/controllers/api/index.js | |||
@@ -2,6 +2,8 @@ | |||
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | 4 | ||
5 | const utils = require('../../helpers/utils') | ||
6 | |||
5 | const router = express.Router() | 7 | const router = express.Router() |
6 | 8 | ||
7 | const clientsController = require('./clients') | 9 | const clientsController = require('./clients') |
@@ -18,7 +20,7 @@ router.use('/requests', requestsController) | |||
18 | router.use('/users', usersController) | 20 | router.use('/users', usersController) |
19 | router.use('/videos', videosController) | 21 | router.use('/videos', videosController) |
20 | router.use('/ping', pong) | 22 | router.use('/ping', pong) |
21 | router.use('/*', badRequest) | 23 | router.use('/*', utils.badRequest) |
22 | 24 | ||
23 | // --------------------------------------------------------------------------- | 25 | // --------------------------------------------------------------------------- |
24 | 26 | ||
@@ -29,7 +31,3 @@ module.exports = router | |||
29 | function pong (req, res, next) { | 31 | function pong (req, res, next) { |
30 | return res.send('pong').status(200).end() | 32 | return res.send('pong').status(200).end() |
31 | } | 33 | } |
32 | |||
33 | function badRequest (req, res, next) { | ||
34 | res.type('json').status(400).end() | ||
35 | } | ||
diff --git a/server/controllers/api/pods.js b/server/controllers/api/pods.js index 7857fcee0..38702face 100644 --- a/server/controllers/api/pods.js +++ b/server/controllers/api/pods.js | |||
@@ -1,10 +1,11 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | const waterfall = require('async/waterfall') | 4 | const waterfall = require('async/waterfall') |
6 | 5 | ||
6 | const db = require('../../initializers/database') | ||
7 | const logger = require('../../helpers/logger') | 7 | const logger = require('../../helpers/logger') |
8 | const utils = require('../../helpers/utils') | ||
8 | const friends = require('../../lib/friends') | 9 | const friends = require('../../lib/friends') |
9 | const middlewares = require('../../middlewares') | 10 | const middlewares = require('../../middlewares') |
10 | const admin = middlewares.admin | 11 | const admin = middlewares.admin |
@@ -15,7 +16,6 @@ const validators = middlewares.validators.pods | |||
15 | const signatureValidator = middlewares.validators.remote.signature | 16 | const signatureValidator = middlewares.validators.remote.signature |
16 | 17 | ||
17 | const router = express.Router() | 18 | const router = express.Router() |
18 | const Pod = mongoose.model('Pod') | ||
19 | 19 | ||
20 | router.get('/', listPods) | 20 | router.get('/', listPods) |
21 | router.post('/', | 21 | router.post('/', |
@@ -37,7 +37,7 @@ router.get('/quitfriends', | |||
37 | ) | 37 | ) |
38 | // Post because this is a secured request | 38 | // Post because this is a secured request |
39 | router.post('/remove', | 39 | router.post('/remove', |
40 | signatureValidator, | 40 | signatureValidator.signature, |
41 | checkSignature, | 41 | checkSignature, |
42 | removePods | 42 | removePods |
43 | ) | 43 | ) |
@@ -53,15 +53,15 @@ function addPods (req, res, next) { | |||
53 | 53 | ||
54 | waterfall([ | 54 | waterfall([ |
55 | function addPod (callback) { | 55 | function addPod (callback) { |
56 | const pod = new Pod(informations) | 56 | const pod = db.Pod.build(informations) |
57 | pod.save(function (err, podCreated) { | 57 | pod.save().asCallback(function (err, podCreated) { |
58 | // Be sure about the number of parameters for the callback | 58 | // Be sure about the number of parameters for the callback |
59 | return callback(err, podCreated) | 59 | return callback(err, podCreated) |
60 | }) | 60 | }) |
61 | }, | 61 | }, |
62 | 62 | ||
63 | function sendMyVideos (podCreated, callback) { | 63 | function sendMyVideos (podCreated, callback) { |
64 | friends.sendOwnedVideosToPod(podCreated._id) | 64 | friends.sendOwnedVideosToPod(podCreated.id) |
65 | 65 | ||
66 | callback(null) | 66 | callback(null) |
67 | }, | 67 | }, |
@@ -84,10 +84,10 @@ function addPods (req, res, next) { | |||
84 | } | 84 | } |
85 | 85 | ||
86 | function listPods (req, res, next) { | 86 | function listPods (req, res, next) { |
87 | Pod.list(function (err, podsList) { | 87 | db.Pod.list(function (err, podsList) { |
88 | if (err) return next(err) | 88 | if (err) return next(err) |
89 | 89 | ||
90 | res.json(getFormatedPods(podsList)) | 90 | res.json(utils.getFormatedObjects(podsList, podsList.length)) |
91 | }) | 91 | }) |
92 | } | 92 | } |
93 | 93 | ||
@@ -111,11 +111,11 @@ function removePods (req, res, next) { | |||
111 | 111 | ||
112 | waterfall([ | 112 | waterfall([ |
113 | function loadPod (callback) { | 113 | function loadPod (callback) { |
114 | Pod.loadByHost(host, callback) | 114 | db.Pod.loadByHost(host, callback) |
115 | }, | 115 | }, |
116 | 116 | ||
117 | function removePod (pod, callback) { | 117 | function deletePod (pod, callback) { |
118 | pod.remove(callback) | 118 | pod.destroy().asCallback(callback) |
119 | } | 119 | } |
120 | ], function (err) { | 120 | ], function (err) { |
121 | if (err) return next(err) | 121 | if (err) return next(err) |
@@ -131,15 +131,3 @@ function quitFriends (req, res, next) { | |||
131 | res.type('json').status(204).end() | 131 | res.type('json').status(204).end() |
132 | }) | 132 | }) |
133 | } | 133 | } |
134 | |||
135 | // --------------------------------------------------------------------------- | ||
136 | |||
137 | function getFormatedPods (pods) { | ||
138 | const formatedPods = [] | ||
139 | |||
140 | pods.forEach(function (pod) { | ||
141 | formatedPods.push(pod.toFormatedJSON()) | ||
142 | }) | ||
143 | |||
144 | return formatedPods | ||
145 | } | ||
diff --git a/server/controllers/api/remote.js b/server/controllers/api/remote.js deleted file mode 100644 index f1046c534..000000000 --- a/server/controllers/api/remote.js +++ /dev/null | |||
@@ -1,86 +0,0 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const each = require('async/each') | ||
4 | const eachSeries = require('async/eachSeries') | ||
5 | const express = require('express') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
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 | const Video = mongoose.model('Video') | ||
15 | |||
16 | router.post('/videos', | ||
17 | validators.signature, | ||
18 | secureMiddleware.checkSignature, | ||
19 | validators.remoteVideos, | ||
20 | remoteVideos | ||
21 | ) | ||
22 | |||
23 | // --------------------------------------------------------------------------- | ||
24 | |||
25 | module.exports = router | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | function remoteVideos (req, res, next) { | ||
30 | const requests = req.body.data | ||
31 | const fromHost = req.body.signature.host | ||
32 | |||
33 | // We need to process in the same order to keep consistency | ||
34 | // TODO: optimization | ||
35 | eachSeries(requests, function (request, callbackEach) { | ||
36 | const videoData = request.data | ||
37 | |||
38 | if (request.type === 'add') { | ||
39 | addRemoteVideo(videoData, fromHost, callbackEach) | ||
40 | } else if (request.type === 'remove') { | ||
41 | removeRemoteVideo(videoData, fromHost, callbackEach) | ||
42 | } else { | ||
43 | logger.error('Unkown remote request type %s.', request.type) | ||
44 | } | ||
45 | }, function (err) { | ||
46 | if (err) logger.error('Error managing remote videos.', { error: err }) | ||
47 | }) | ||
48 | |||
49 | // We don't need to keep the other pod waiting | ||
50 | return res.type('json').status(204).end() | ||
51 | } | ||
52 | |||
53 | function addRemoteVideo (videoToCreateData, fromHost, callback) { | ||
54 | logger.debug('Adding remote video "%s".', videoToCreateData.name) | ||
55 | |||
56 | const video = new Video(videoToCreateData) | ||
57 | video.podHost = fromHost | ||
58 | Video.generateThumbnailFromBase64(video, videoToCreateData.thumbnailBase64, function (err) { | ||
59 | if (err) { | ||
60 | logger.error('Cannot generate thumbnail from base 64 data.', { error: err }) | ||
61 | return callback(err) | ||
62 | } | ||
63 | |||
64 | video.save(callback) | ||
65 | }) | ||
66 | } | ||
67 | |||
68 | function removeRemoteVideo (videoToRemoveData, fromHost, callback) { | ||
69 | // We need the list because we have to remove some other stuffs (thumbnail etc) | ||
70 | Video.listByHostAndRemoteId(fromHost, videoToRemoveData.remoteId, function (err, videosList) { | ||
71 | if (err) { | ||
72 | logger.error('Cannot list videos from host and magnets.', { error: err }) | ||
73 | return callback(err) | ||
74 | } | ||
75 | |||
76 | if (videosList.length === 0) { | ||
77 | logger.error('No remote video was found for this pod.', { magnetUri: videoToRemoveData.magnetUri, podHost: fromHost }) | ||
78 | } | ||
79 | |||
80 | each(videosList, function (video, callbackEach) { | ||
81 | logger.debug('Removing remote video %s.', video.magnetUri) | ||
82 | |||
83 | video.remove(callbackEach) | ||
84 | }, callback) | ||
85 | }) | ||
86 | } | ||
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 | |||
3 | const express = require('express') | ||
4 | |||
5 | const utils = require('../../../helpers/utils') | ||
6 | |||
7 | const router = express.Router() | ||
8 | |||
9 | const videosRemoteController = require('./videos') | ||
10 | |||
11 | router.use('/videos', videosRemoteController) | ||
12 | router.use('/*', utils.badRequest) | ||
13 | |||
14 | // --------------------------------------------------------------------------- | ||
15 | |||
16 | module.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 | |||
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 videosValidators = middlewares.validators.remote.videos | ||
11 | const signatureValidators = middlewares.validators.remote.signature | ||
12 | const logger = require('../../../helpers/logger') | ||
13 | const utils = require('../../../helpers/utils') | ||
14 | |||
15 | const router = express.Router() | ||
16 | |||
17 | router.post('/', | ||
18 | signatureValidators.signature, | ||
19 | secureMiddleware.checkSignature, | ||
20 | videosValidators.remoteVideos, | ||
21 | remoteVideos | ||
22 | ) | ||
23 | |||
24 | // --------------------------------------------------------------------------- | ||
25 | |||
26 | module.exports = router | ||
27 | |||
28 | // --------------------------------------------------------------------------- | ||
29 | |||
30 | function 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 | ||
68 | function 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 | |||
84 | function 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 | ||
184 | function 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 | |||
200 | function 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 | |||
270 | function 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 | |||
288 | function 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 | |||
317 | function 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 | } | ||
diff --git a/server/controllers/api/requests.js b/server/controllers/api/requests.js index 52aad6997..1f9193fc8 100644 --- a/server/controllers/api/requests.js +++ b/server/controllers/api/requests.js | |||
@@ -1,15 +1,13 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const mongoose = require('mongoose') | ||
5 | 4 | ||
6 | const constants = require('../../initializers/constants') | 5 | const constants = require('../../initializers/constants') |
6 | const db = require('../../initializers/database') | ||
7 | const middlewares = require('../../middlewares') | 7 | const middlewares = require('../../middlewares') |
8 | const admin = middlewares.admin | 8 | const admin = middlewares.admin |
9 | const oAuth = middlewares.oauth | 9 | const oAuth = middlewares.oauth |
10 | 10 | ||
11 | const Request = mongoose.model('Request') | ||
12 | |||
13 | const router = express.Router() | 11 | const router = express.Router() |
14 | 12 | ||
15 | router.get('/stats', | 13 | router.get('/stats', |
@@ -25,13 +23,13 @@ module.exports = router | |||
25 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
26 | 24 | ||
27 | function getStatsRequests (req, res, next) { | 25 | function getStatsRequests (req, res, next) { |
28 | Request.list(function (err, requests) { | 26 | db.Request.countTotalRequests(function (err, totalRequests) { |
29 | if (err) return next(err) | 27 | if (err) return next(err) |
30 | 28 | ||
31 | return res.json({ | 29 | return res.json({ |
32 | requests: requests, | 30 | totalRequests: totalRequests, |
33 | maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL, | 31 | maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL, |
34 | remainingMilliSeconds: Request.remainingMilliSeconds(), | 32 | remainingMilliSeconds: db.Request.remainingMilliSeconds(), |
35 | milliSecondsInterval: constants.REQUESTS_INTERVAL | 33 | milliSecondsInterval: constants.REQUESTS_INTERVAL |
36 | }) | 34 | }) |
37 | }) | 35 | }) |
diff --git a/server/controllers/api/users.js b/server/controllers/api/users.js index b4d687312..6cd0e84f7 100644 --- a/server/controllers/api/users.js +++ b/server/controllers/api/users.js | |||
@@ -1,13 +1,12 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const each = require('async/each') | ||
4 | const express = require('express') | 3 | const express = require('express') |
5 | const mongoose = require('mongoose') | ||
6 | const waterfall = require('async/waterfall') | 4 | const waterfall = require('async/waterfall') |
7 | 5 | ||
8 | const constants = require('../../initializers/constants') | 6 | const constants = require('../../initializers/constants') |
9 | const friends = require('../../lib/friends') | 7 | const db = require('../../initializers/database') |
10 | const logger = require('../../helpers/logger') | 8 | const logger = require('../../helpers/logger') |
9 | const utils = require('../../helpers/utils') | ||
11 | const middlewares = require('../../middlewares') | 10 | const middlewares = require('../../middlewares') |
12 | const admin = middlewares.admin | 11 | const admin = middlewares.admin |
13 | const oAuth = middlewares.oauth | 12 | const oAuth = middlewares.oauth |
@@ -17,9 +16,6 @@ const validatorsPagination = middlewares.validators.pagination | |||
17 | const validatorsSort = middlewares.validators.sort | 16 | const validatorsSort = middlewares.validators.sort |
18 | const validatorsUsers = middlewares.validators.users | 17 | const validatorsUsers = middlewares.validators.users |
19 | 18 | ||
20 | const User = mongoose.model('User') | ||
21 | const Video = mongoose.model('Video') | ||
22 | |||
23 | const router = express.Router() | 19 | const router = express.Router() |
24 | 20 | ||
25 | router.get('/me', oAuth.authenticate, getUserInformation) | 21 | router.get('/me', oAuth.authenticate, getUserInformation) |
@@ -62,13 +58,13 @@ module.exports = router | |||
62 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
63 | 59 | ||
64 | function createUser (req, res, next) { | 60 | function createUser (req, res, next) { |
65 | const user = new User({ | 61 | const user = db.User.build({ |
66 | username: req.body.username, | 62 | username: req.body.username, |
67 | password: req.body.password, | 63 | password: req.body.password, |
68 | role: constants.USER_ROLES.USER | 64 | role: constants.USER_ROLES.USER |
69 | }) | 65 | }) |
70 | 66 | ||
71 | user.save(function (err, createdUser) { | 67 | user.save().asCallback(function (err, createdUser) { |
72 | if (err) return next(err) | 68 | if (err) return next(err) |
73 | 69 | ||
74 | return res.type('json').status(204).end() | 70 | return res.type('json').status(204).end() |
@@ -76,7 +72,7 @@ function createUser (req, res, next) { | |||
76 | } | 72 | } |
77 | 73 | ||
78 | function getUserInformation (req, res, next) { | 74 | function getUserInformation (req, res, next) { |
79 | User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { | 75 | db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { |
80 | if (err) return next(err) | 76 | if (err) return next(err) |
81 | 77 | ||
82 | return res.json(user.toFormatedJSON()) | 78 | return res.json(user.toFormatedJSON()) |
@@ -84,48 +80,21 @@ function getUserInformation (req, res, next) { | |||
84 | } | 80 | } |
85 | 81 | ||
86 | function listUsers (req, res, next) { | 82 | function listUsers (req, res, next) { |
87 | User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { | 83 | db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { |
88 | if (err) return next(err) | 84 | if (err) return next(err) |
89 | 85 | ||
90 | res.json(getFormatedUsers(usersList, usersTotal)) | 86 | res.json(utils.getFormatedObjects(usersList, usersTotal)) |
91 | }) | 87 | }) |
92 | } | 88 | } |
93 | 89 | ||
94 | function removeUser (req, res, next) { | 90 | function removeUser (req, res, next) { |
95 | waterfall([ | 91 | waterfall([ |
96 | function getUser (callback) { | 92 | function loadUser (callback) { |
97 | User.loadById(req.params.id, callback) | 93 | db.User.loadById(req.params.id, callback) |
98 | }, | ||
99 | |||
100 | function getVideos (user, callback) { | ||
101 | Video.listOwnedByAuthor(user.username, function (err, videos) { | ||
102 | return callback(err, user, videos) | ||
103 | }) | ||
104 | }, | ||
105 | |||
106 | function removeVideosFromDB (user, videos, callback) { | ||
107 | each(videos, function (video, callbackEach) { | ||
108 | video.remove(callbackEach) | ||
109 | }, function (err) { | ||
110 | return callback(err, user, videos) | ||
111 | }) | ||
112 | }, | ||
113 | |||
114 | function sendInformationToFriends (user, videos, callback) { | ||
115 | videos.forEach(function (video) { | ||
116 | const params = { | ||
117 | name: video.name, | ||
118 | magnetUri: video.magnetUri | ||
119 | } | ||
120 | |||
121 | friends.removeVideoToFriends(params) | ||
122 | }) | ||
123 | |||
124 | return callback(null, user) | ||
125 | }, | 94 | }, |
126 | 95 | ||
127 | function removeUserFromDB (user, callback) { | 96 | function deleteUser (user, callback) { |
128 | user.remove(callback) | 97 | user.destroy().asCallback(callback) |
129 | } | 98 | } |
130 | ], function andFinally (err) { | 99 | ], function andFinally (err) { |
131 | if (err) { | 100 | if (err) { |
@@ -138,11 +107,11 @@ function removeUser (req, res, next) { | |||
138 | } | 107 | } |
139 | 108 | ||
140 | function updateUser (req, res, next) { | 109 | function updateUser (req, res, next) { |
141 | User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { | 110 | db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { |
142 | if (err) return next(err) | 111 | if (err) return next(err) |
143 | 112 | ||
144 | user.password = req.body.password | 113 | user.password = req.body.password |
145 | user.save(function (err) { | 114 | user.save().asCallback(function (err) { |
146 | if (err) return next(err) | 115 | if (err) return next(err) |
147 | 116 | ||
148 | return res.sendStatus(204) | 117 | return res.sendStatus(204) |
@@ -153,18 +122,3 @@ function updateUser (req, res, next) { | |||
153 | function success (req, res, next) { | 122 | function success (req, res, next) { |
154 | res.end() | 123 | res.end() |
155 | } | 124 | } |
156 | |||
157 | // --------------------------------------------------------------------------- | ||
158 | |||
159 | function getFormatedUsers (users, usersTotal) { | ||
160 | const formatedUsers = [] | ||
161 | |||
162 | users.forEach(function (user) { | ||
163 | formatedUsers.push(user.toFormatedJSON()) | ||
164 | }) | ||
165 | |||
166 | return { | ||
167 | total: usersTotal, | ||
168 | data: formatedUsers | ||
169 | } | ||
170 | } | ||
diff --git a/server/controllers/api/videos.js b/server/controllers/api/videos.js index daf452573..2c4af520e 100644 --- a/server/controllers/api/videos.js +++ b/server/controllers/api/videos.js | |||
@@ -2,15 +2,16 @@ | |||
2 | 2 | ||
3 | const express = require('express') | 3 | const express = require('express') |
4 | const fs = require('fs') | 4 | const fs = require('fs') |
5 | const mongoose = require('mongoose') | ||
6 | const multer = require('multer') | 5 | const multer = require('multer') |
7 | const path = require('path') | 6 | const path = require('path') |
8 | const waterfall = require('async/waterfall') | 7 | const waterfall = require('async/waterfall') |
9 | 8 | ||
10 | const constants = require('../../initializers/constants') | 9 | const constants = require('../../initializers/constants') |
10 | const db = require('../../initializers/database') | ||
11 | const logger = require('../../helpers/logger') | 11 | const logger = require('../../helpers/logger') |
12 | const friends = require('../../lib/friends') | 12 | const friends = require('../../lib/friends') |
13 | const middlewares = require('../../middlewares') | 13 | const middlewares = require('../../middlewares') |
14 | const admin = middlewares.admin | ||
14 | const oAuth = middlewares.oauth | 15 | const oAuth = middlewares.oauth |
15 | const pagination = middlewares.pagination | 16 | const pagination = middlewares.pagination |
16 | const validators = middlewares.validators | 17 | const validators = middlewares.validators |
@@ -22,7 +23,6 @@ const sort = middlewares.sort | |||
22 | const utils = require('../../helpers/utils') | 23 | const utils = require('../../helpers/utils') |
23 | 24 | ||
24 | const router = express.Router() | 25 | const router = express.Router() |
25 | const Video = mongoose.model('Video') | ||
26 | 26 | ||
27 | // multer configuration | 27 | // multer configuration |
28 | const storage = multer.diskStorage({ | 28 | const storage = multer.diskStorage({ |
@@ -44,6 +44,21 @@ const storage = multer.diskStorage({ | |||
44 | 44 | ||
45 | const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }]) | 45 | const reqFiles = multer({ storage: storage }).fields([{ name: 'videofile', maxCount: 1 }]) |
46 | 46 | ||
47 | router.get('/abuse', | ||
48 | oAuth.authenticate, | ||
49 | admin.ensureIsAdmin, | ||
50 | validatorsPagination.pagination, | ||
51 | validatorsSort.videoAbusesSort, | ||
52 | sort.setVideoAbusesSort, | ||
53 | pagination.setPagination, | ||
54 | listVideoAbuses | ||
55 | ) | ||
56 | router.post('/:id/abuse', | ||
57 | oAuth.authenticate, | ||
58 | validatorsVideos.videoAbuseReport, | ||
59 | reportVideoAbuseRetryWrapper | ||
60 | ) | ||
61 | |||
47 | router.get('/', | 62 | router.get('/', |
48 | validatorsPagination.pagination, | 63 | validatorsPagination.pagination, |
49 | validatorsSort.videosSort, | 64 | validatorsSort.videosSort, |
@@ -51,11 +66,17 @@ router.get('/', | |||
51 | pagination.setPagination, | 66 | pagination.setPagination, |
52 | listVideos | 67 | listVideos |
53 | ) | 68 | ) |
69 | router.put('/:id', | ||
70 | oAuth.authenticate, | ||
71 | reqFiles, | ||
72 | validatorsVideos.videosUpdate, | ||
73 | updateVideoRetryWrapper | ||
74 | ) | ||
54 | router.post('/', | 75 | router.post('/', |
55 | oAuth.authenticate, | 76 | oAuth.authenticate, |
56 | reqFiles, | 77 | reqFiles, |
57 | validatorsVideos.videosAdd, | 78 | validatorsVideos.videosAdd, |
58 | addVideo | 79 | addVideoRetryWrapper |
59 | ) | 80 | ) |
60 | router.get('/:id', | 81 | router.get('/:id', |
61 | validatorsVideos.videosGet, | 82 | validatorsVideos.videosGet, |
@@ -82,117 +103,264 @@ module.exports = router | |||
82 | 103 | ||
83 | // --------------------------------------------------------------------------- | 104 | // --------------------------------------------------------------------------- |
84 | 105 | ||
85 | function addVideo (req, res, next) { | 106 | // Wrapper to video add that retry the function if there is a database error |
86 | const videoFile = req.files.videofile[0] | 107 | // We need this because we run the transaction in SERIALIZABLE isolation that can fail |
108 | function addVideoRetryWrapper (req, res, next) { | ||
109 | utils.transactionRetryer( | ||
110 | function (callback) { | ||
111 | return addVideo(req, res, req.files.videofile[0], callback) | ||
112 | }, | ||
113 | function (err) { | ||
114 | if (err) { | ||
115 | logger.error('Cannot insert the video with many retries.', { error: err }) | ||
116 | return next(err) | ||
117 | } | ||
118 | |||
119 | // TODO : include Location of the new video -> 201 | ||
120 | return res.type('json').status(204).end() | ||
121 | } | ||
122 | ) | ||
123 | } | ||
124 | |||
125 | function addVideo (req, res, videoFile, callback) { | ||
87 | const videoInfos = req.body | 126 | const videoInfos = req.body |
88 | 127 | ||
89 | waterfall([ | 128 | waterfall([ |
90 | function createVideoObject (callback) { | ||
91 | const id = mongoose.Types.ObjectId() | ||
92 | 129 | ||
130 | function startTransaction (callbackWaterfall) { | ||
131 | db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { | ||
132 | return callbackWaterfall(err, t) | ||
133 | }) | ||
134 | }, | ||
135 | |||
136 | function findOrCreateAuthor (t, callbackWaterfall) { | ||
137 | const user = res.locals.oauth.token.User | ||
138 | |||
139 | const name = user.username | ||
140 | // null because it is OUR pod | ||
141 | const podId = null | ||
142 | const userId = user.id | ||
143 | |||
144 | db.Author.findOrCreateAuthor(name, podId, userId, t, function (err, authorInstance) { | ||
145 | return callbackWaterfall(err, t, authorInstance) | ||
146 | }) | ||
147 | }, | ||
148 | |||
149 | function findOrCreateTags (t, author, callbackWaterfall) { | ||
150 | const tags = videoInfos.tags | ||
151 | |||
152 | db.Tag.findOrCreateTags(tags, t, function (err, tagInstances) { | ||
153 | return callbackWaterfall(err, t, author, tagInstances) | ||
154 | }) | ||
155 | }, | ||
156 | |||
157 | function createVideoObject (t, author, tagInstances, callbackWaterfall) { | ||
93 | const videoData = { | 158 | const videoData = { |
94 | _id: id, | ||
95 | name: videoInfos.name, | 159 | name: videoInfos.name, |
96 | remoteId: null, | 160 | remoteId: null, |
97 | extname: path.extname(videoFile.filename), | 161 | extname: path.extname(videoFile.filename), |
98 | description: videoInfos.description, | 162 | description: videoInfos.description, |
99 | author: res.locals.oauth.token.user.username, | ||
100 | duration: videoFile.duration, | 163 | duration: videoFile.duration, |
101 | tags: videoInfos.tags | 164 | authorId: author.id |
102 | } | 165 | } |
103 | 166 | ||
104 | const video = new Video(videoData) | 167 | const video = db.Video.build(videoData) |
105 | 168 | ||
106 | return callback(null, video) | 169 | return callbackWaterfall(null, t, author, tagInstances, video) |
107 | }, | 170 | }, |
108 | 171 | ||
109 | // Set the videoname the same as the MongoDB id | 172 | // Set the videoname the same as the id |
110 | function renameVideoFile (video, callback) { | 173 | function renameVideoFile (t, author, tagInstances, video, callbackWaterfall) { |
111 | const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR | 174 | const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR |
112 | const source = path.join(videoDir, videoFile.filename) | 175 | const source = path.join(videoDir, videoFile.filename) |
113 | const destination = path.join(videoDir, video.getVideoFilename()) | 176 | const destination = path.join(videoDir, video.getVideoFilename()) |
114 | 177 | ||
115 | fs.rename(source, destination, function (err) { | 178 | fs.rename(source, destination, function (err) { |
116 | return callback(err, video) | 179 | if (err) return callbackWaterfall(err) |
180 | |||
181 | // This is important in case if there is another attempt | ||
182 | videoFile.filename = video.getVideoFilename() | ||
183 | return callbackWaterfall(null, t, author, tagInstances, video) | ||
184 | }) | ||
185 | }, | ||
186 | |||
187 | function insertVideoIntoDB (t, author, tagInstances, video, callbackWaterfall) { | ||
188 | const options = { transaction: t } | ||
189 | |||
190 | // Add tags association | ||
191 | video.save(options).asCallback(function (err, videoCreated) { | ||
192 | if (err) return callbackWaterfall(err) | ||
193 | |||
194 | // Do not forget to add Author informations to the created video | ||
195 | videoCreated.Author = author | ||
196 | |||
197 | return callbackWaterfall(err, t, tagInstances, videoCreated) | ||
117 | }) | 198 | }) |
118 | }, | 199 | }, |
119 | 200 | ||
120 | function insertIntoDB (video, callback) { | 201 | function associateTagsToVideo (t, tagInstances, video, callbackWaterfall) { |
121 | video.save(function (err, video) { | 202 | const options = { transaction: t } |
122 | // Assert there are only one argument sent to the next function (video) | 203 | |
123 | return callback(err, video) | 204 | video.setTags(tagInstances, options).asCallback(function (err) { |
205 | video.Tags = tagInstances | ||
206 | |||
207 | return callbackWaterfall(err, t, video) | ||
124 | }) | 208 | }) |
125 | }, | 209 | }, |
126 | 210 | ||
127 | function sendToFriends (video, callback) { | 211 | function sendToFriends (t, video, callbackWaterfall) { |
128 | video.toRemoteJSON(function (err, remoteVideo) { | 212 | video.toAddRemoteJSON(function (err, remoteVideo) { |
129 | if (err) return callback(err) | 213 | if (err) return callbackWaterfall(err) |
130 | 214 | ||
131 | // Now we'll add the video's meta data to our friends | 215 | // Now we'll add the video's meta data to our friends |
132 | friends.addVideoToFriends(remoteVideo) | 216 | friends.addVideoToFriends(remoteVideo, t, function (err) { |
133 | 217 | return callbackWaterfall(err, t) | |
134 | return callback(null) | 218 | }) |
135 | }) | 219 | }) |
136 | } | 220 | } |
137 | 221 | ||
138 | ], function andFinally (err) { | 222 | ], function andFinally (err, t) { |
139 | if (err) { | 223 | if (err) { |
140 | logger.error('Cannot insert the video.') | 224 | // This is just a debug because we will retry the insert |
141 | return next(err) | 225 | logger.debug('Cannot insert the video.', { error: err }) |
226 | |||
227 | // Abort transaction? | ||
228 | if (t) t.rollback() | ||
229 | |||
230 | return callback(err) | ||
142 | } | 231 | } |
143 | 232 | ||
144 | // TODO : include Location of the new video -> 201 | 233 | // Commit transaction |
145 | return res.type('json').status(204).end() | 234 | t.commit().asCallback(function (err) { |
235 | if (err) return callback(err) | ||
236 | |||
237 | logger.info('Video with name %s created.', videoInfos.name) | ||
238 | return callback(null) | ||
239 | }) | ||
146 | }) | 240 | }) |
147 | } | 241 | } |
148 | 242 | ||
149 | function getVideo (req, res, next) { | 243 | function updateVideoRetryWrapper (req, res, next) { |
150 | Video.load(req.params.id, function (err, video) { | 244 | utils.transactionRetryer( |
151 | if (err) return next(err) | 245 | function (callback) { |
246 | return updateVideo(req, res, callback) | ||
247 | }, | ||
248 | function (err) { | ||
249 | if (err) { | ||
250 | logger.error('Cannot update the video with many retries.', { error: err }) | ||
251 | return next(err) | ||
252 | } | ||
152 | 253 | ||
153 | if (!video) { | 254 | // TODO : include Location of the new video -> 201 |
154 | return res.type('json').status(204).end() | 255 | return res.type('json').status(204).end() |
155 | } | 256 | } |
156 | 257 | ) | |
157 | res.json(video.toFormatedJSON()) | ||
158 | }) | ||
159 | } | 258 | } |
160 | 259 | ||
161 | function listVideos (req, res, next) { | 260 | function updateVideo (req, res, finalCallback) { |
162 | Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { | 261 | const videoInstance = res.locals.video |
163 | if (err) return next(err) | 262 | const videoFieldsSave = videoInstance.toJSON() |
263 | const videoInfosToUpdate = req.body | ||
164 | 264 | ||
165 | res.json(getFormatedVideos(videosList, videosTotal)) | 265 | waterfall([ |
166 | }) | ||
167 | } | ||
168 | 266 | ||
169 | function removeVideo (req, res, next) { | 267 | function startTransaction (callback) { |
170 | const videoId = req.params.id | 268 | db.sequelize.transaction({ isolationLevel: 'SERIALIZABLE' }).asCallback(function (err, t) { |
269 | return callback(err, t) | ||
270 | }) | ||
271 | }, | ||
171 | 272 | ||
172 | waterfall([ | 273 | function findOrCreateTags (t, callback) { |
173 | function getVideo (callback) { | 274 | if (videoInfosToUpdate.tags) { |
174 | Video.load(videoId, callback) | 275 | db.Tag.findOrCreateTags(videoInfosToUpdate.tags, t, function (err, tagInstances) { |
276 | return callback(err, t, tagInstances) | ||
277 | }) | ||
278 | } else { | ||
279 | return callback(null, t, null) | ||
280 | } | ||
175 | }, | 281 | }, |
176 | 282 | ||
177 | function removeFromDB (video, callback) { | 283 | function updateVideoIntoDB (t, tagInstances, callback) { |
178 | video.remove(function (err) { | 284 | const options = { |
179 | if (err) return callback(err) | 285 | transaction: t |
286 | } | ||
287 | |||
288 | if (videoInfosToUpdate.name) videoInstance.set('name', videoInfosToUpdate.name) | ||
289 | if (videoInfosToUpdate.description) videoInstance.set('description', videoInfosToUpdate.description) | ||
180 | 290 | ||
181 | return callback(null, video) | 291 | videoInstance.save(options).asCallback(function (err) { |
292 | return callback(err, t, tagInstances) | ||
182 | }) | 293 | }) |
183 | }, | 294 | }, |
184 | 295 | ||
185 | function sendInformationToFriends (video, callback) { | 296 | function associateTagsToVideo (t, tagInstances, callback) { |
186 | const params = { | 297 | if (tagInstances) { |
187 | name: video.name, | 298 | const options = { transaction: t } |
188 | remoteId: video._id | 299 | |
300 | videoInstance.setTags(tagInstances, options).asCallback(function (err) { | ||
301 | videoInstance.Tags = tagInstances | ||
302 | |||
303 | return callback(err, t) | ||
304 | }) | ||
305 | } else { | ||
306 | return callback(null, t) | ||
189 | } | 307 | } |
308 | }, | ||
190 | 309 | ||
191 | friends.removeVideoToFriends(params) | 310 | function sendToFriends (t, callback) { |
311 | const json = videoInstance.toUpdateRemoteJSON() | ||
192 | 312 | ||
193 | return callback(null) | 313 | // Now we'll update the video's meta data to our friends |
314 | friends.updateVideoToFriends(json, t, function (err) { | ||
315 | return callback(err, t) | ||
316 | }) | ||
317 | } | ||
318 | |||
319 | ], function andFinally (err, t) { | ||
320 | if (err) { | ||
321 | logger.debug('Cannot update the video.', { error: err }) | ||
322 | |||
323 | // Abort transaction? | ||
324 | if (t) t.rollback() | ||
325 | |||
326 | // Force fields we want to update | ||
327 | // If the transaction is retried, sequelize will think the object has not changed | ||
328 | // So it will skip the SQL request, even if the last one was ROLLBACKed! | ||
329 | Object.keys(videoFieldsSave).forEach(function (key) { | ||
330 | const value = videoFieldsSave[key] | ||
331 | videoInstance.set(key, value) | ||
332 | }) | ||
333 | |||
334 | return finalCallback(err) | ||
194 | } | 335 | } |
195 | ], function andFinally (err) { | 336 | |
337 | // Commit transaction | ||
338 | t.commit().asCallback(function (err) { | ||
339 | if (err) return finalCallback(err) | ||
340 | |||
341 | logger.info('Video with name %s updated.', videoInfosToUpdate.name) | ||
342 | return finalCallback(null) | ||
343 | }) | ||
344 | }) | ||
345 | } | ||
346 | |||
347 | function getVideo (req, res, next) { | ||
348 | const videoInstance = res.locals.video | ||
349 | res.json(videoInstance.toFormatedJSON()) | ||
350 | } | ||
351 | |||
352 | function listVideos (req, res, next) { | ||
353 | db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { | ||
354 | if (err) return next(err) | ||
355 | |||
356 | res.json(utils.getFormatedObjects(videosList, videosTotal)) | ||
357 | }) | ||
358 | } | ||
359 | |||
360 | function removeVideo (req, res, next) { | ||
361 | const videoInstance = res.locals.video | ||
362 | |||
363 | videoInstance.destroy().asCallback(function (err) { | ||
196 | if (err) { | 364 | if (err) { |
197 | logger.error('Errors when removed the video.', { error: err }) | 365 | logger.error('Errors when removed the video.', { error: err }) |
198 | return next(err) | 366 | return next(err) |
@@ -203,25 +371,97 @@ function removeVideo (req, res, next) { | |||
203 | } | 371 | } |
204 | 372 | ||
205 | function searchVideos (req, res, next) { | 373 | function searchVideos (req, res, next) { |
206 | Video.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, | 374 | db.Video.searchAndPopulateAuthorAndPodAndTags( |
207 | function (err, videosList, videosTotal) { | 375 | req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, |
376 | function (err, videosList, videosTotal) { | ||
377 | if (err) return next(err) | ||
378 | |||
379 | res.json(utils.getFormatedObjects(videosList, videosTotal)) | ||
380 | } | ||
381 | ) | ||
382 | } | ||
383 | |||
384 | function listVideoAbuses (req, res, next) { | ||
385 | db.VideoAbuse.listForApi(req.query.start, req.query.count, req.query.sort, function (err, abusesList, abusesTotal) { | ||
208 | if (err) return next(err) | 386 | if (err) return next(err) |
209 | 387 | ||
210 | res.json(getFormatedVideos(videosList, videosTotal)) | 388 | res.json(utils.getFormatedObjects(abusesList, abusesTotal)) |
211 | }) | 389 | }) |
212 | } | 390 | } |
213 | 391 | ||
214 | // --------------------------------------------------------------------------- | 392 | function reportVideoAbuseRetryWrapper (req, res, next) { |
393 | utils.transactionRetryer( | ||
394 | function (callback) { | ||
395 | return reportVideoAbuse(req, res, callback) | ||
396 | }, | ||
397 | function (err) { | ||
398 | if (err) { | ||
399 | logger.error('Cannot report abuse to the video with many retries.', { error: err }) | ||
400 | return next(err) | ||
401 | } | ||
215 | 402 | ||
216 | function getFormatedVideos (videos, videosTotal) { | 403 | return res.type('json').status(204).end() |
217 | const formatedVideos = [] | 404 | } |
405 | ) | ||
406 | } | ||
218 | 407 | ||
219 | videos.forEach(function (video) { | 408 | function reportVideoAbuse (req, res, finalCallback) { |
220 | formatedVideos.push(video.toFormatedJSON()) | 409 | const videoInstance = res.locals.video |
221 | }) | 410 | const reporterUsername = res.locals.oauth.token.User.username |
222 | 411 | ||
223 | return { | 412 | const abuse = { |
224 | total: videosTotal, | 413 | reporterUsername, |
225 | data: formatedVideos | 414 | reason: req.body.reason, |
415 | videoId: videoInstance.id, | ||
416 | reporterPodId: null // This is our pod that reported this abuse | ||
226 | } | 417 | } |
418 | |||
419 | waterfall([ | ||
420 | |||
421 | function startTransaction (callback) { | ||
422 | db.sequelize.transaction().asCallback(function (err, t) { | ||
423 | return callback(err, t) | ||
424 | }) | ||
425 | }, | ||
426 | |||
427 | function createAbuse (t, callback) { | ||
428 | db.VideoAbuse.create(abuse).asCallback(function (err, abuse) { | ||
429 | return callback(err, t, abuse) | ||
430 | }) | ||
431 | }, | ||
432 | |||
433 | function sendToFriendsIfNeeded (t, abuse, callback) { | ||
434 | // We send the information to the destination pod | ||
435 | if (videoInstance.isOwned() === false) { | ||
436 | const reportData = { | ||
437 | reporterUsername, | ||
438 | reportReason: abuse.reason, | ||
439 | videoRemoteId: videoInstance.remoteId | ||
440 | } | ||
441 | |||
442 | friends.reportAbuseVideoToFriend(reportData, videoInstance) | ||
443 | } | ||
444 | |||
445 | return callback(null, t) | ||
446 | } | ||
447 | |||
448 | ], function andFinally (err, t) { | ||
449 | if (err) { | ||
450 | logger.debug('Cannot update the video.', { error: err }) | ||
451 | |||
452 | // Abort transaction? | ||
453 | if (t) t.rollback() | ||
454 | |||
455 | return finalCallback(err) | ||
456 | } | ||
457 | |||
458 | // Commit transaction | ||
459 | t.commit().asCallback(function (err) { | ||
460 | if (err) return finalCallback(err) | ||
461 | |||
462 | logger.info('Abuse report for video %s created.', videoInstance.name) | ||
463 | return finalCallback(null) | ||
464 | }) | ||
465 | }) | ||
227 | } | 466 | } |
467 | |||
diff --git a/server/controllers/client.js b/server/controllers/client.js index 572db6133..8c242af07 100644 --- a/server/controllers/client.js +++ b/server/controllers/client.js | |||
@@ -3,13 +3,12 @@ | |||
3 | const parallel = require('async/parallel') | 3 | const parallel = require('async/parallel') |
4 | const express = require('express') | 4 | const express = require('express') |
5 | const fs = require('fs') | 5 | const fs = require('fs') |
6 | const mongoose = require('mongoose') | ||
7 | const path = require('path') | 6 | const path = require('path') |
8 | const validator = require('express-validator').validator | 7 | const validator = require('express-validator').validator |
9 | 8 | ||
10 | const constants = require('../initializers/constants') | 9 | const constants = require('../initializers/constants') |
10 | const db = require('../initializers/database') | ||
11 | 11 | ||
12 | const Video = mongoose.model('Video') | ||
13 | const router = express.Router() | 12 | const router = express.Router() |
14 | 13 | ||
15 | const opengraphComment = '<!-- opengraph tags -->' | 14 | const opengraphComment = '<!-- opengraph tags -->' |
@@ -45,14 +44,14 @@ function addOpenGraphTags (htmlStringPage, video) { | |||
45 | if (video.isOwned()) { | 44 | if (video.isOwned()) { |
46 | basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL | 45 | basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL |
47 | } else { | 46 | } else { |
48 | basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.podHost | 47 | basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host |
49 | } | 48 | } |
50 | 49 | ||
51 | // We fetch the remote preview (bigger than the thumbnail) | 50 | // We fetch the remote preview (bigger than the thumbnail) |
52 | // This should not overhead the remote server since social websites put in a cache the OpenGraph tags | 51 | // This should not overhead the remote server since social websites put in a cache the OpenGraph tags |
53 | // We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example) | 52 | // We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example) |
54 | const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName() | 53 | const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName() |
55 | const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video._id | 54 | const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video.id |
56 | 55 | ||
57 | const metaTags = { | 56 | const metaTags = { |
58 | 'og:type': 'video', | 57 | 'og:type': 'video', |
@@ -86,7 +85,7 @@ function generateWatchHtmlPage (req, res, next) { | |||
86 | const videoId = req.params.id | 85 | const videoId = req.params.id |
87 | 86 | ||
88 | // Let Angular application handle errors | 87 | // Let Angular application handle errors |
89 | if (!validator.isMongoId(videoId)) return res.sendFile(indexPath) | 88 | if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath) |
90 | 89 | ||
91 | parallel({ | 90 | parallel({ |
92 | file: function (callback) { | 91 | file: function (callback) { |
@@ -94,7 +93,7 @@ function generateWatchHtmlPage (req, res, next) { | |||
94 | }, | 93 | }, |
95 | 94 | ||
96 | video: function (callback) { | 95 | video: function (callback) { |
97 | Video.load(videoId, callback) | 96 | db.Video.loadAndPopulateAuthorAndPodAndTags(videoId, callback) |
98 | } | 97 | } |
99 | }, function (err, results) { | 98 | }, function (err, results) { |
100 | if (err) return next(err) | 99 | if (err) return next(err) |
diff --git a/server/helpers/custom-validators/index.js b/server/helpers/custom-validators/index.js index 96b5b20b9..9383e0304 100644 --- a/server/helpers/custom-validators/index.js +++ b/server/helpers/custom-validators/index.js | |||
@@ -2,12 +2,14 @@ | |||
2 | 2 | ||
3 | const miscValidators = require('./misc') | 3 | const miscValidators = require('./misc') |
4 | const podsValidators = require('./pods') | 4 | const podsValidators = require('./pods') |
5 | const remoteValidators = require('./remote') | ||
5 | const usersValidators = require('./users') | 6 | const usersValidators = require('./users') |
6 | const videosValidators = require('./videos') | 7 | const videosValidators = require('./videos') |
7 | 8 | ||
8 | const validators = { | 9 | const validators = { |
9 | misc: miscValidators, | 10 | misc: miscValidators, |
10 | pods: podsValidators, | 11 | pods: podsValidators, |
12 | remote: remoteValidators, | ||
11 | users: usersValidators, | 13 | users: usersValidators, |
12 | videos: videosValidators | 14 | videos: videosValidators |
13 | } | 15 | } |
diff --git a/server/helpers/custom-validators/pods.js b/server/helpers/custom-validators/pods.js index 0154a2424..8bb3733ff 100644 --- a/server/helpers/custom-validators/pods.js +++ b/server/helpers/custom-validators/pods.js | |||
@@ -5,14 +5,19 @@ const validator = require('express-validator').validator | |||
5 | const miscValidators = require('./misc') | 5 | const miscValidators = require('./misc') |
6 | 6 | ||
7 | const podsValidators = { | 7 | const podsValidators = { |
8 | isEachUniqueHostValid | 8 | isEachUniqueHostValid, |
9 | isHostValid | ||
10 | } | ||
11 | |||
12 | function isHostValid (host) { | ||
13 | return validator.isURL(host) && host.split('://').length === 1 | ||
9 | } | 14 | } |
10 | 15 | ||
11 | function isEachUniqueHostValid (hosts) { | 16 | function isEachUniqueHostValid (hosts) { |
12 | return miscValidators.isArray(hosts) && | 17 | return miscValidators.isArray(hosts) && |
13 | hosts.length !== 0 && | 18 | hosts.length !== 0 && |
14 | hosts.every(function (host) { | 19 | hosts.every(function (host) { |
15 | return validator.isURL(host) && host.split('://').length === 1 && hosts.indexOf(host) === hosts.lastIndexOf(host) | 20 | return isHostValid(host) && hosts.indexOf(host) === hosts.lastIndexOf(host) |
16 | }) | 21 | }) |
17 | } | 22 | } |
18 | 23 | ||
diff --git a/server/helpers/custom-validators/remote/index.js b/server/helpers/custom-validators/remote/index.js new file mode 100644 index 000000000..1939a95f4 --- /dev/null +++ b/server/helpers/custom-validators/remote/index.js | |||
@@ -0,0 +1,11 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const remoteVideosValidators = require('./videos') | ||
4 | |||
5 | const validators = { | ||
6 | videos: remoteVideosValidators | ||
7 | } | ||
8 | |||
9 | // --------------------------------------------------------------------------- | ||
10 | |||
11 | module.exports = validators | ||
diff --git a/server/helpers/custom-validators/remote/videos.js b/server/helpers/custom-validators/remote/videos.js new file mode 100644 index 000000000..7c27b9dbb --- /dev/null +++ b/server/helpers/custom-validators/remote/videos.js | |||
@@ -0,0 +1,73 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const videosValidators = require('../videos') | ||
4 | const miscValidators = require('../misc') | ||
5 | |||
6 | const remoteVideosValidators = { | ||
7 | isEachRemoteRequestVideosValid | ||
8 | } | ||
9 | |||
10 | function isEachRemoteRequestVideosValid (requests) { | ||
11 | return miscValidators.isArray(requests) && | ||
12 | requests.every(function (request) { | ||
13 | const video = request.data | ||
14 | return ( | ||
15 | isRequestTypeAddValid(request.type) && | ||
16 | videosValidators.isVideoAuthorValid(video.author) && | ||
17 | videosValidators.isVideoDateValid(video.createdAt) && | ||
18 | videosValidators.isVideoDateValid(video.updatedAt) && | ||
19 | videosValidators.isVideoDescriptionValid(video.description) && | ||
20 | videosValidators.isVideoDurationValid(video.duration) && | ||
21 | videosValidators.isVideoInfoHashValid(video.infoHash) && | ||
22 | videosValidators.isVideoNameValid(video.name) && | ||
23 | videosValidators.isVideoTagsValid(video.tags) && | ||
24 | videosValidators.isVideoThumbnailDataValid(video.thumbnailData) && | ||
25 | videosValidators.isVideoRemoteIdValid(video.remoteId) && | ||
26 | videosValidators.isVideoExtnameValid(video.extname) | ||
27 | ) || | ||
28 | ( | ||
29 | isRequestTypeUpdateValid(request.type) && | ||
30 | videosValidators.isVideoDateValid(video.createdAt) && | ||
31 | videosValidators.isVideoDateValid(video.updatedAt) && | ||
32 | videosValidators.isVideoDescriptionValid(video.description) && | ||
33 | videosValidators.isVideoDurationValid(video.duration) && | ||
34 | videosValidators.isVideoInfoHashValid(video.infoHash) && | ||
35 | videosValidators.isVideoNameValid(video.name) && | ||
36 | videosValidators.isVideoTagsValid(video.tags) && | ||
37 | videosValidators.isVideoRemoteIdValid(video.remoteId) && | ||
38 | videosValidators.isVideoExtnameValid(video.extname) | ||
39 | ) || | ||
40 | ( | ||
41 | isRequestTypeRemoveValid(request.type) && | ||
42 | videosValidators.isVideoRemoteIdValid(video.remoteId) | ||
43 | ) || | ||
44 | ( | ||
45 | isRequestTypeReportAbuseValid(request.type) && | ||
46 | videosValidators.isVideoRemoteIdValid(request.data.videoRemoteId) && | ||
47 | videosValidators.isVideoAbuseReasonValid(request.data.reportReason) && | ||
48 | videosValidators.isVideoAbuseReporterUsernameValid(request.data.reporterUsername) | ||
49 | ) | ||
50 | }) | ||
51 | } | ||
52 | |||
53 | // --------------------------------------------------------------------------- | ||
54 | |||
55 | module.exports = remoteVideosValidators | ||
56 | |||
57 | // --------------------------------------------------------------------------- | ||
58 | |||
59 | function isRequestTypeAddValid (value) { | ||
60 | return value === 'add' | ||
61 | } | ||
62 | |||
63 | function isRequestTypeUpdateValid (value) { | ||
64 | return value === 'update' | ||
65 | } | ||
66 | |||
67 | function isRequestTypeRemoveValid (value) { | ||
68 | return value === 'remove' | ||
69 | } | ||
70 | |||
71 | function isRequestTypeReportAbuseValid (value) { | ||
72 | return value === 'report-abuse' | ||
73 | } | ||
diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js index 1a7753265..7f727854d 100644 --- a/server/helpers/custom-validators/videos.js +++ b/server/helpers/custom-validators/videos.js | |||
@@ -6,43 +6,22 @@ const constants = require('../../initializers/constants') | |||
6 | const usersValidators = require('./users') | 6 | const usersValidators = require('./users') |
7 | const miscValidators = require('./misc') | 7 | const miscValidators = require('./misc') |
8 | const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS | 8 | const VIDEOS_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEOS |
9 | const VIDEO_ABUSES_CONSTRAINTS_FIELDS = constants.CONSTRAINTS_FIELDS.VIDEO_ABUSES | ||
9 | 10 | ||
10 | const videosValidators = { | 11 | const videosValidators = { |
11 | isEachRemoteVideosValid, | ||
12 | isVideoAuthorValid, | 12 | isVideoAuthorValid, |
13 | isVideoDateValid, | 13 | isVideoDateValid, |
14 | isVideoDescriptionValid, | 14 | isVideoDescriptionValid, |
15 | isVideoDurationValid, | 15 | isVideoDurationValid, |
16 | isVideoMagnetValid, | 16 | isVideoInfoHashValid, |
17 | isVideoNameValid, | 17 | isVideoNameValid, |
18 | isVideoPodHostValid, | ||
19 | isVideoTagsValid, | 18 | isVideoTagsValid, |
20 | isVideoThumbnailValid, | 19 | isVideoThumbnailValid, |
21 | isVideoThumbnail64Valid | 20 | isVideoThumbnailDataValid, |
22 | } | 21 | isVideoExtnameValid, |
23 | 22 | isVideoRemoteIdValid, | |
24 | function isEachRemoteVideosValid (requests) { | 23 | isVideoAbuseReasonValid, |
25 | return miscValidators.isArray(requests) && | 24 | isVideoAbuseReporterUsernameValid |
26 | requests.every(function (request) { | ||
27 | const video = request.data | ||
28 | return ( | ||
29 | isRequestTypeAddValid(request.type) && | ||
30 | isVideoAuthorValid(video.author) && | ||
31 | isVideoDateValid(video.createdDate) && | ||
32 | isVideoDescriptionValid(video.description) && | ||
33 | isVideoDurationValid(video.duration) && | ||
34 | isVideoMagnetValid(video.magnet) && | ||
35 | isVideoNameValid(video.name) && | ||
36 | isVideoTagsValid(video.tags) && | ||
37 | isVideoThumbnail64Valid(video.thumbnailBase64) && | ||
38 | isVideoRemoteIdValid(video.remoteId) | ||
39 | ) || | ||
40 | ( | ||
41 | isRequestTypeRemoveValid(request.type) && | ||
42 | isVideoNameValid(video.name) && | ||
43 | isVideoRemoteIdValid(video.remoteId) | ||
44 | ) | ||
45 | }) | ||
46 | } | 25 | } |
47 | 26 | ||
48 | function isVideoAuthorValid (value) { | 27 | function isVideoAuthorValid (value) { |
@@ -61,17 +40,16 @@ function isVideoDurationValid (value) { | |||
61 | return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) | 40 | return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) |
62 | } | 41 | } |
63 | 42 | ||
64 | function isVideoMagnetValid (value) { | 43 | function isVideoExtnameValid (value) { |
65 | return validator.isLength(value.infoHash, VIDEOS_CONSTRAINTS_FIELDS.MAGNET.INFO_HASH) | 44 | return VIDEOS_CONSTRAINTS_FIELDS.EXTNAME.indexOf(value) !== -1 |
66 | } | 45 | } |
67 | 46 | ||
68 | function isVideoNameValid (value) { | 47 | function isVideoInfoHashValid (value) { |
69 | return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) | 48 | return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH) |
70 | } | 49 | } |
71 | 50 | ||
72 | function isVideoPodHostValid (value) { | 51 | function isVideoNameValid (value) { |
73 | // TODO: set options (TLD...) | 52 | return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.NAME) |
74 | return validator.isURL(value) | ||
75 | } | 53 | } |
76 | 54 | ||
77 | function isVideoTagsValid (tags) { | 55 | function isVideoTagsValid (tags) { |
@@ -87,25 +65,22 @@ function isVideoThumbnailValid (value) { | |||
87 | return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL) | 65 | return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL) |
88 | } | 66 | } |
89 | 67 | ||
90 | function isVideoThumbnail64Valid (value) { | 68 | function isVideoThumbnailDataValid (value) { |
91 | return validator.isBase64(value) && | 69 | return validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL_DATA) |
92 | validator.isByteLength(value, VIDEOS_CONSTRAINTS_FIELDS.THUMBNAIL64) | ||
93 | } | 70 | } |
94 | 71 | ||
95 | function isVideoRemoteIdValid (value) { | 72 | function isVideoRemoteIdValid (value) { |
96 | return validator.isMongoId(value) | 73 | return validator.isUUID(value, 4) |
97 | } | 74 | } |
98 | 75 | ||
99 | // --------------------------------------------------------------------------- | 76 | function isVideoAbuseReasonValid (value) { |
77 | return validator.isLength(value, VIDEO_ABUSES_CONSTRAINTS_FIELDS.REASON) | ||
78 | } | ||
100 | 79 | ||
101 | module.exports = videosValidators | 80 | function isVideoAbuseReporterUsernameValid (value) { |
81 | return usersValidators.isUserUsernameValid(value) | ||
82 | } | ||
102 | 83 | ||
103 | // --------------------------------------------------------------------------- | 84 | // --------------------------------------------------------------------------- |
104 | 85 | ||
105 | function isRequestTypeAddValid (value) { | 86 | module.exports = videosValidators |
106 | return value === 'add' | ||
107 | } | ||
108 | |||
109 | function isRequestTypeRemoveValid (value) { | ||
110 | return value === 'remove' | ||
111 | } | ||
diff --git a/server/helpers/logger.js b/server/helpers/logger.js index fcc1789fd..281acedb8 100644 --- a/server/helpers/logger.js +++ b/server/helpers/logger.js | |||
@@ -22,7 +22,8 @@ const logger = new winston.Logger({ | |||
22 | json: true, | 22 | json: true, |
23 | maxsize: 5242880, | 23 | maxsize: 5242880, |
24 | maxFiles: 5, | 24 | maxFiles: 5, |
25 | colorize: false | 25 | colorize: false, |
26 | prettyPrint: true | ||
26 | }), | 27 | }), |
27 | new winston.transports.Console({ | 28 | new winston.transports.Console({ |
28 | level: 'debug', | 29 | level: 'debug', |
@@ -30,7 +31,8 @@ const logger = new winston.Logger({ | |||
30 | handleExceptions: true, | 31 | handleExceptions: true, |
31 | humanReadableUnhandledException: true, | 32 | humanReadableUnhandledException: true, |
32 | json: false, | 33 | json: false, |
33 | colorize: true | 34 | colorize: true, |
35 | prettyPrint: true | ||
34 | }) | 36 | }) |
35 | ], | 37 | ], |
36 | exitOnError: true | 38 | exitOnError: true |
diff --git a/server/helpers/peertube-crypto.js b/server/helpers/peertube-crypto.js index 2e07df00e..0f1e02ad6 100644 --- a/server/helpers/peertube-crypto.js +++ b/server/helpers/peertube-crypto.js | |||
@@ -1,16 +1,13 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const bcrypt = require('bcrypt') | ||
4 | const crypto = require('crypto') | 3 | const crypto = require('crypto') |
4 | const bcrypt = require('bcrypt') | ||
5 | const fs = require('fs') | 5 | const fs = require('fs') |
6 | const openssl = require('openssl-wrapper') | 6 | const openssl = require('openssl-wrapper') |
7 | const ursa = require('ursa') | ||
8 | 7 | ||
9 | const constants = require('../initializers/constants') | 8 | const constants = require('../initializers/constants') |
10 | const logger = require('./logger') | 9 | const logger = require('./logger') |
11 | 10 | ||
12 | const algorithm = 'aes-256-ctr' | ||
13 | |||
14 | const peertubeCrypto = { | 11 | const peertubeCrypto = { |
15 | checkSignature, | 12 | checkSignature, |
16 | comparePassword, | 13 | comparePassword, |
@@ -19,12 +16,51 @@ const peertubeCrypto = { | |||
19 | sign | 16 | sign |
20 | } | 17 | } |
21 | 18 | ||
22 | function checkSignature (publicKey, rawData, hexSignature) { | 19 | function checkSignature (publicKey, data, hexSignature) { |
23 | const crt = ursa.createPublicKey(publicKey) | 20 | const verify = crypto.createVerify(constants.SIGNATURE_ALGORITHM) |
24 | const isValid = crt.hashAndVerify('sha256', new Buffer(rawData).toString('hex'), hexSignature, 'hex') | 21 | |
22 | let dataString | ||
23 | if (typeof data === 'string') { | ||
24 | dataString = data | ||
25 | } else { | ||
26 | try { | ||
27 | dataString = JSON.stringify(data) | ||
28 | } catch (err) { | ||
29 | logger.error('Cannot check signature.', { error: err }) | ||
30 | return false | ||
31 | } | ||
32 | } | ||
33 | |||
34 | verify.update(dataString, 'utf8') | ||
35 | |||
36 | const isValid = verify.verify(publicKey, hexSignature, constants.SIGNATURE_ENCODING) | ||
25 | return isValid | 37 | return isValid |
26 | } | 38 | } |
27 | 39 | ||
40 | function sign (data) { | ||
41 | const sign = crypto.createSign(constants.SIGNATURE_ALGORITHM) | ||
42 | |||
43 | let dataString | ||
44 | if (typeof data === 'string') { | ||
45 | dataString = data | ||
46 | } else { | ||
47 | try { | ||
48 | dataString = JSON.stringify(data) | ||
49 | } catch (err) { | ||
50 | logger.error('Cannot sign data.', { error: err }) | ||
51 | return '' | ||
52 | } | ||
53 | } | ||
54 | |||
55 | sign.update(dataString, 'utf8') | ||
56 | |||
57 | // TODO: make async | ||
58 | const myKey = fs.readFileSync(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem') | ||
59 | const signature = sign.sign(myKey, constants.SIGNATURE_ENCODING) | ||
60 | |||
61 | return signature | ||
62 | } | ||
63 | |||
28 | function comparePassword (plainPassword, hashPassword, callback) { | 64 | function comparePassword (plainPassword, hashPassword, callback) { |
29 | bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { | 65 | bcrypt.compare(plainPassword, hashPassword, function (err, isPasswordMatch) { |
30 | if (err) return callback(err) | 66 | if (err) return callback(err) |
@@ -55,13 +91,6 @@ function cryptPassword (password, callback) { | |||
55 | }) | 91 | }) |
56 | } | 92 | } |
57 | 93 | ||
58 | function sign (data) { | ||
59 | const myKey = ursa.createPrivateKey(fs.readFileSync(constants.CONFIG.STORAGE.CERT_DIR + 'peertube.key.pem')) | ||
60 | const signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex') | ||
61 | |||
62 | return signature | ||
63 | } | ||
64 | |||
65 | // --------------------------------------------------------------------------- | 94 | // --------------------------------------------------------------------------- |
66 | 95 | ||
67 | module.exports = peertubeCrypto | 96 | module.exports = peertubeCrypto |
@@ -113,11 +142,3 @@ function createCerts (callback) { | |||
113 | }) | 142 | }) |
114 | }) | 143 | }) |
115 | } | 144 | } |
116 | |||
117 | function generatePassword (callback) { | ||
118 | crypto.randomBytes(32, function (err, buf) { | ||
119 | if (err) return callback(err) | ||
120 | |||
121 | callback(null, buf.toString('utf8')) | ||
122 | }) | ||
123 | } | ||
diff --git a/server/helpers/requests.js b/server/helpers/requests.js index b0cda09fe..095b95e1c 100644 --- a/server/helpers/requests.js +++ b/server/helpers/requests.js | |||
@@ -28,31 +28,37 @@ function makeSecureRequest (params, callback) { | |||
28 | url: constants.REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path | 28 | url: constants.REMOTE_SCHEME.HTTP + '://' + params.toPod.host + params.path |
29 | } | 29 | } |
30 | 30 | ||
31 | // Add data with POST requst ? | 31 | if (params.method !== 'POST') { |
32 | if (params.method === 'POST') { | 32 | return callback(new Error('Cannot make a secure request with a non POST method.')) |
33 | requestParams.json = {} | 33 | } |
34 | 34 | ||
35 | // Add signature if it is specified in the params | 35 | requestParams.json = {} |
36 | if (params.sign === true) { | ||
37 | const host = constants.CONFIG.WEBSERVER.HOST | ||
38 | |||
39 | requestParams.json.signature = { | ||
40 | host, | ||
41 | signature: peertubeCrypto.sign(host) | ||
42 | } | ||
43 | } | ||
44 | 36 | ||
45 | // If there are data informations | 37 | // Add signature if it is specified in the params |
38 | if (params.sign === true) { | ||
39 | const host = constants.CONFIG.WEBSERVER.HOST | ||
40 | |||
41 | let dataToSign | ||
46 | if (params.data) { | 42 | if (params.data) { |
47 | requestParams.json.data = params.data | 43 | dataToSign = dataToSign = params.data |
48 | request.post(requestParams, callback) | ||
49 | } else { | 44 | } else { |
50 | // No data | 45 | // We do not have data to sign so we just take our host |
51 | request.post(requestParams, callback) | 46 | // It is not ideal but the connection should be in HTTPS |
47 | dataToSign = host | ||
52 | } | 48 | } |
53 | } else { | 49 | |
54 | request.get(requestParams, callback) | 50 | requestParams.json.signature = { |
51 | host, // Which host we pretend to be | ||
52 | signature: peertubeCrypto.sign(dataToSign) | ||
53 | } | ||
54 | } | ||
55 | |||
56 | // If there are data informations | ||
57 | if (params.data) { | ||
58 | requestParams.json.data = params.data | ||
55 | } | 59 | } |
60 | |||
61 | request.post(requestParams, callback) | ||
56 | } | 62 | } |
57 | 63 | ||
58 | // --------------------------------------------------------------------------- | 64 | // --------------------------------------------------------------------------- |
diff --git a/server/helpers/utils.js b/server/helpers/utils.js index 9f27671b6..a902850cd 100644 --- a/server/helpers/utils.js +++ b/server/helpers/utils.js | |||
@@ -1,13 +1,21 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const crypto = require('crypto') | 3 | const crypto = require('crypto') |
4 | const retry = require('async/retry') | ||
4 | 5 | ||
5 | const logger = require('./logger') | 6 | const logger = require('./logger') |
6 | 7 | ||
7 | const utils = { | 8 | const utils = { |
9 | badRequest, | ||
8 | cleanForExit, | 10 | cleanForExit, |
9 | generateRandomString, | 11 | generateRandomString, |
10 | isTestInstance | 12 | isTestInstance, |
13 | getFormatedObjects, | ||
14 | transactionRetryer | ||
15 | } | ||
16 | |||
17 | function badRequest (req, res, next) { | ||
18 | res.type('json').status(400).end() | ||
11 | } | 19 | } |
12 | 20 | ||
13 | function generateRandomString (size, callback) { | 21 | function generateRandomString (size, callback) { |
@@ -27,6 +35,31 @@ function isTestInstance () { | |||
27 | return (process.env.NODE_ENV === 'test') | 35 | return (process.env.NODE_ENV === 'test') |
28 | } | 36 | } |
29 | 37 | ||
38 | function getFormatedObjects (objects, objectsTotal) { | ||
39 | const formatedObjects = [] | ||
40 | |||
41 | objects.forEach(function (object) { | ||
42 | formatedObjects.push(object.toFormatedJSON()) | ||
43 | }) | ||
44 | |||
45 | return { | ||
46 | total: objectsTotal, | ||
47 | data: formatedObjects | ||
48 | } | ||
49 | } | ||
50 | |||
51 | function transactionRetryer (func, callback) { | ||
52 | retry({ | ||
53 | times: 5, | ||
54 | |||
55 | errorFilter: function (err) { | ||
56 | const willRetry = (err.name === 'SequelizeDatabaseError') | ||
57 | logger.debug('Maybe retrying the transaction function.', { willRetry }) | ||
58 | return willRetry | ||
59 | } | ||
60 | }, func, callback) | ||
61 | } | ||
62 | |||
30 | // --------------------------------------------------------------------------- | 63 | // --------------------------------------------------------------------------- |
31 | 64 | ||
32 | module.exports = utils | 65 | module.exports = utils |
diff --git a/server/initializers/checker.js b/server/initializers/checker.js index aea013fa9..6471bb4f1 100644 --- a/server/initializers/checker.js +++ b/server/initializers/checker.js | |||
@@ -1,10 +1,8 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | 3 | const config = require('config') |
4 | const mongoose = require('mongoose') | ||
5 | 4 | ||
6 | const Client = mongoose.model('OAuthClient') | 5 | const db = require('./database') |
7 | const User = mongoose.model('User') | ||
8 | 6 | ||
9 | const checker = { | 7 | const checker = { |
10 | checkConfig, | 8 | checkConfig, |
@@ -29,7 +27,7 @@ function checkConfig () { | |||
29 | function checkMissedConfig () { | 27 | function checkMissedConfig () { |
30 | const required = [ 'listen.port', | 28 | const required = [ 'listen.port', |
31 | 'webserver.https', 'webserver.hostname', 'webserver.port', | 29 | 'webserver.https', 'webserver.hostname', 'webserver.port', |
32 | 'database.hostname', 'database.port', 'database.suffix', | 30 | 'database.hostname', 'database.port', 'database.suffix', 'database.username', 'database.password', |
33 | 'storage.certs', 'storage.videos', 'storage.logs', 'storage.thumbnails', 'storage.previews' | 31 | 'storage.certs', 'storage.videos', 'storage.logs', 'storage.thumbnails', 'storage.previews' |
34 | ] | 32 | ] |
35 | const miss = [] | 33 | const miss = [] |
@@ -44,15 +42,15 @@ function checkMissedConfig () { | |||
44 | } | 42 | } |
45 | 43 | ||
46 | function clientsExist (callback) { | 44 | function clientsExist (callback) { |
47 | Client.list(function (err, clients) { | 45 | db.OAuthClient.countTotal(function (err, totalClients) { |
48 | if (err) return callback(err) | 46 | if (err) return callback(err) |
49 | 47 | ||
50 | return callback(null, clients.length !== 0) | 48 | return callback(null, totalClients !== 0) |
51 | }) | 49 | }) |
52 | } | 50 | } |
53 | 51 | ||
54 | function usersExist (callback) { | 52 | function usersExist (callback) { |
55 | User.countTotal(function (err, totalUsers) { | 53 | db.User.countTotal(function (err, totalUsers) { |
56 | if (err) return callback(err) | 54 | if (err) return callback(err) |
57 | 55 | ||
58 | return callback(null, totalUsers !== 0) | 56 | return callback(null, totalUsers !== 0) |
diff --git a/server/initializers/constants.js b/server/initializers/constants.js index 3ddf87454..97e3c5296 100644 --- a/server/initializers/constants.js +++ b/server/initializers/constants.js | |||
@@ -1,7 +1,6 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const config = require('config') | 3 | const config = require('config') |
4 | const maxBy = require('lodash/maxBy') | ||
5 | const path = require('path') | 4 | const path = require('path') |
6 | 5 | ||
7 | // --------------------------------------------------------------------------- | 6 | // --------------------------------------------------------------------------- |
@@ -14,13 +13,14 @@ const PAGINATION_COUNT_DEFAULT = 15 | |||
14 | 13 | ||
15 | // Sortable columns per schema | 14 | // Sortable columns per schema |
16 | const SEARCHABLE_COLUMNS = { | 15 | const SEARCHABLE_COLUMNS = { |
17 | VIDEOS: [ 'name', 'magnetUri', 'podHost', 'author', 'tags' ] | 16 | VIDEOS: [ 'name', 'magnetUri', 'host', 'author', 'tags' ] |
18 | } | 17 | } |
19 | 18 | ||
20 | // Sortable columns per schema | 19 | // Sortable columns per schema |
21 | const SORTABLE_COLUMNS = { | 20 | const SORTABLE_COLUMNS = { |
22 | USERS: [ 'username', '-username', 'createdDate', '-createdDate' ], | 21 | USERS: [ 'username', '-username', 'createdAt', '-createdAt' ], |
23 | VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ] | 22 | VIDEO_ABUSES: [ 'createdAt', '-createdAt' ], |
23 | VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdAt', '-createdAt' ] | ||
24 | } | 24 | } |
25 | 25 | ||
26 | const OAUTH_LIFETIME = { | 26 | const OAUTH_LIFETIME = { |
@@ -37,7 +37,9 @@ const CONFIG = { | |||
37 | DATABASE: { | 37 | DATABASE: { |
38 | DBNAME: 'peertube' + config.get('database.suffix'), | 38 | DBNAME: 'peertube' + config.get('database.suffix'), |
39 | HOSTNAME: config.get('database.hostname'), | 39 | HOSTNAME: config.get('database.hostname'), |
40 | PORT: config.get('database.port') | 40 | PORT: config.get('database.port'), |
41 | USERNAME: config.get('database.username'), | ||
42 | PASSWORD: config.get('database.password') | ||
41 | }, | 43 | }, |
42 | STORAGE: { | 44 | STORAGE: { |
43 | CERT_DIR: path.join(__dirname, '..', '..', config.get('storage.certs')), | 45 | CERT_DIR: path.join(__dirname, '..', '..', config.get('storage.certs')), |
@@ -64,17 +66,19 @@ const CONSTRAINTS_FIELDS = { | |||
64 | USERNAME: { min: 3, max: 20 }, // Length | 66 | USERNAME: { min: 3, max: 20 }, // Length |
65 | PASSWORD: { min: 6, max: 255 } // Length | 67 | PASSWORD: { min: 6, max: 255 } // Length |
66 | }, | 68 | }, |
69 | VIDEO_ABUSES: { | ||
70 | REASON: { min: 2, max: 300 } // Length | ||
71 | }, | ||
67 | VIDEOS: { | 72 | VIDEOS: { |
68 | NAME: { min: 3, max: 50 }, // Length | 73 | NAME: { min: 3, max: 50 }, // Length |
69 | DESCRIPTION: { min: 3, max: 250 }, // Length | 74 | DESCRIPTION: { min: 3, max: 250 }, // Length |
70 | MAGNET: { | 75 | EXTNAME: [ '.mp4', '.ogv', '.webm' ], |
71 | INFO_HASH: { min: 10, max: 50 } // Length | 76 | INFO_HASH: { min: 40, max: 40 }, // Length, infohash is 20 bytes length but we represent it in hexa so 20 * 2 |
72 | }, | ||
73 | DURATION: { min: 1, max: 7200 }, // Number | 77 | DURATION: { min: 1, max: 7200 }, // Number |
74 | TAGS: { min: 1, max: 3 }, // Number of total tags | 78 | TAGS: { min: 1, max: 3 }, // Number of total tags |
75 | TAG: { min: 2, max: 10 }, // Length | 79 | TAG: { min: 2, max: 10 }, // Length |
76 | THUMBNAIL: { min: 2, max: 30 }, | 80 | THUMBNAIL: { min: 2, max: 30 }, |
77 | THUMBNAIL64: { min: 0, max: 20000 } // Bytes | 81 | THUMBNAIL_DATA: { min: 0, max: 20000 } // Bytes |
78 | } | 82 | } |
79 | } | 83 | } |
80 | 84 | ||
@@ -88,41 +92,7 @@ const FRIEND_SCORE = { | |||
88 | 92 | ||
89 | // --------------------------------------------------------------------------- | 93 | // --------------------------------------------------------------------------- |
90 | 94 | ||
91 | const MONGO_MIGRATION_SCRIPTS = [ | 95 | const LAST_MIGRATION_VERSION = 0 |
92 | { | ||
93 | script: '0005-create-application', | ||
94 | version: 5 | ||
95 | }, | ||
96 | { | ||
97 | script: '0010-users-password', | ||
98 | version: 10 | ||
99 | }, | ||
100 | { | ||
101 | script: '0015-admin-role', | ||
102 | version: 15 | ||
103 | }, | ||
104 | { | ||
105 | script: '0020-requests-endpoint', | ||
106 | version: 20 | ||
107 | }, | ||
108 | { | ||
109 | script: '0025-video-filenames', | ||
110 | version: 25 | ||
111 | }, | ||
112 | { | ||
113 | script: '0030-video-magnet', | ||
114 | version: 30 | ||
115 | }, | ||
116 | { | ||
117 | script: '0035-url-to-host', | ||
118 | version: 35 | ||
119 | }, | ||
120 | { | ||
121 | script: '0040-video-remote-id', | ||
122 | version: 40 | ||
123 | } | ||
124 | ] | ||
125 | const LAST_MONGO_SCHEMA_VERSION = (maxBy(MONGO_MIGRATION_SCRIPTS, 'version'))['version'] | ||
126 | 96 | ||
127 | // --------------------------------------------------------------------------- | 97 | // --------------------------------------------------------------------------- |
128 | 98 | ||
@@ -138,8 +108,10 @@ let REQUESTS_INTERVAL = 600000 | |||
138 | // Number of requests in parallel we can make | 108 | // Number of requests in parallel we can make |
139 | const REQUESTS_IN_PARALLEL = 10 | 109 | const REQUESTS_IN_PARALLEL = 10 |
140 | 110 | ||
141 | // How many requests we put in request | 111 | // To how many pods we send requests |
142 | const REQUESTS_LIMIT = 10 | 112 | const REQUESTS_LIMIT_PODS = 10 |
113 | // How many requests we send to a pod per interval | ||
114 | const REQUESTS_LIMIT_PER_POD = 5 | ||
143 | 115 | ||
144 | // Number of requests to retry for replay requests module | 116 | // Number of requests to retry for replay requests module |
145 | const RETRY_REQUESTS = 5 | 117 | const RETRY_REQUESTS = 5 |
@@ -148,16 +120,21 @@ const REQUEST_ENDPOINTS = { | |||
148 | VIDEOS: 'videos' | 120 | VIDEOS: 'videos' |
149 | } | 121 | } |
150 | 122 | ||
151 | // --------------------------------------------------------------------------- | ||
152 | |||
153 | const REMOTE_SCHEME = { | 123 | const REMOTE_SCHEME = { |
154 | HTTP: 'https', | 124 | HTTP: 'https', |
155 | WS: 'wss' | 125 | WS: 'wss' |
156 | } | 126 | } |
157 | 127 | ||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | const SIGNATURE_ALGORITHM = 'RSA-SHA256' | ||
131 | const SIGNATURE_ENCODING = 'hex' | ||
132 | |||
158 | // Password encryption | 133 | // Password encryption |
159 | const BCRYPT_SALT_SIZE = 10 | 134 | const BCRYPT_SALT_SIZE = 10 |
160 | 135 | ||
136 | // --------------------------------------------------------------------------- | ||
137 | |||
161 | // Express static paths (router) | 138 | // Express static paths (router) |
162 | const STATIC_PATHS = { | 139 | const STATIC_PATHS = { |
163 | PREVIEWS: '/static/previews/', | 140 | PREVIEWS: '/static/previews/', |
@@ -173,6 +150,8 @@ let STATIC_MAX_AGE = '30d' | |||
173 | const THUMBNAILS_SIZE = '200x110' | 150 | const THUMBNAILS_SIZE = '200x110' |
174 | const PREVIEWS_SIZE = '640x480' | 151 | const PREVIEWS_SIZE = '640x480' |
175 | 152 | ||
153 | // --------------------------------------------------------------------------- | ||
154 | |||
176 | const USER_ROLES = { | 155 | const USER_ROLES = { |
177 | ADMIN: 'admin', | 156 | ADMIN: 'admin', |
178 | USER: 'user' | 157 | USER: 'user' |
@@ -198,8 +177,7 @@ module.exports = { | |||
198 | CONFIG, | 177 | CONFIG, |
199 | CONSTRAINTS_FIELDS, | 178 | CONSTRAINTS_FIELDS, |
200 | FRIEND_SCORE, | 179 | FRIEND_SCORE, |
201 | LAST_MONGO_SCHEMA_VERSION, | 180 | LAST_MIGRATION_VERSION, |
202 | MONGO_MIGRATION_SCRIPTS, | ||
203 | OAUTH_LIFETIME, | 181 | OAUTH_LIFETIME, |
204 | PAGINATION_COUNT_DEFAULT, | 182 | PAGINATION_COUNT_DEFAULT, |
205 | PODS_SCORE, | 183 | PODS_SCORE, |
@@ -208,9 +186,12 @@ module.exports = { | |||
208 | REQUEST_ENDPOINTS, | 186 | REQUEST_ENDPOINTS, |
209 | REQUESTS_IN_PARALLEL, | 187 | REQUESTS_IN_PARALLEL, |
210 | REQUESTS_INTERVAL, | 188 | REQUESTS_INTERVAL, |
211 | REQUESTS_LIMIT, | 189 | REQUESTS_LIMIT_PODS, |
190 | REQUESTS_LIMIT_PER_POD, | ||
212 | RETRY_REQUESTS, | 191 | RETRY_REQUESTS, |
213 | SEARCHABLE_COLUMNS, | 192 | SEARCHABLE_COLUMNS, |
193 | SIGNATURE_ALGORITHM, | ||
194 | SIGNATURE_ENCODING, | ||
214 | SORTABLE_COLUMNS, | 195 | SORTABLE_COLUMNS, |
215 | STATIC_MAX_AGE, | 196 | STATIC_MAX_AGE, |
216 | STATIC_PATHS, | 197 | STATIC_PATHS, |
diff --git a/server/initializers/database.js b/server/initializers/database.js index 0564e4e77..f8f68adeb 100644 --- a/server/initializers/database.js +++ b/server/initializers/database.js | |||
@@ -1,37 +1,77 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const mongoose = require('mongoose') | 3 | const fs = require('fs') |
4 | const path = require('path') | ||
5 | const Sequelize = require('sequelize') | ||
4 | 6 | ||
5 | const constants = require('../initializers/constants') | 7 | const constants = require('../initializers/constants') |
6 | const logger = require('../helpers/logger') | 8 | const logger = require('../helpers/logger') |
9 | const utils = require('../helpers/utils') | ||
7 | 10 | ||
8 | // Bootstrap models | 11 | const database = {} |
9 | require('../models/application') | ||
10 | require('../models/oauth-token') | ||
11 | require('../models/user') | ||
12 | require('../models/oauth-client') | ||
13 | require('../models/video') | ||
14 | // Request model needs Video model | ||
15 | require('../models/pods') | ||
16 | // Request model needs Pod model | ||
17 | require('../models/request') | ||
18 | |||
19 | const database = { | ||
20 | connect: connect | ||
21 | } | ||
22 | 12 | ||
23 | function connect () { | 13 | const dbname = constants.CONFIG.DATABASE.DBNAME |
24 | mongoose.Promise = global.Promise | 14 | const username = constants.CONFIG.DATABASE.USERNAME |
25 | mongoose.connect('mongodb://' + constants.CONFIG.DATABASE.HOSTNAME + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME) | 15 | const password = constants.CONFIG.DATABASE.PASSWORD |
26 | mongoose.connection.on('error', function () { | ||
27 | throw new Error('Mongodb connection error.') | ||
28 | }) | ||
29 | 16 | ||
30 | mongoose.connection.on('open', function () { | 17 | const sequelize = new Sequelize(dbname, username, password, { |
31 | logger.info('Connected to mongodb.') | 18 | dialect: 'postgres', |
32 | }) | 19 | host: constants.CONFIG.DATABASE.HOSTNAME, |
33 | } | 20 | port: constants.CONFIG.DATABASE.PORT, |
21 | benchmark: utils.isTestInstance(), | ||
22 | |||
23 | logging: function (message, benchmark) { | ||
24 | let newMessage = message | ||
25 | if (benchmark !== undefined) { | ||
26 | newMessage += ' | ' + benchmark + 'ms' | ||
27 | } | ||
28 | |||
29 | logger.debug(newMessage) | ||
30 | } | ||
31 | }) | ||
32 | |||
33 | database.sequelize = sequelize | ||
34 | database.Sequelize = Sequelize | ||
35 | database.init = init | ||
34 | 36 | ||
35 | // --------------------------------------------------------------------------- | 37 | // --------------------------------------------------------------------------- |
36 | 38 | ||
37 | module.exports = database | 39 | module.exports = database |
40 | |||
41 | // --------------------------------------------------------------------------- | ||
42 | |||
43 | function init (silent, callback) { | ||
44 | if (!callback) { | ||
45 | callback = silent | ||
46 | silent = false | ||
47 | } | ||
48 | |||
49 | if (!callback) callback = function () {} | ||
50 | |||
51 | const modelDirectory = path.join(__dirname, '..', 'models') | ||
52 | fs.readdir(modelDirectory, function (err, files) { | ||
53 | if (err) throw err | ||
54 | |||
55 | files.filter(function (file) { | ||
56 | // For all models but not utils.js | ||
57 | if (file === 'utils.js') return false | ||
58 | |||
59 | return true | ||
60 | }) | ||
61 | .forEach(function (file) { | ||
62 | const model = sequelize.import(path.join(modelDirectory, file)) | ||
63 | |||
64 | database[model.name] = model | ||
65 | }) | ||
66 | |||
67 | Object.keys(database).forEach(function (modelName) { | ||
68 | if ('associate' in database[modelName]) { | ||
69 | database[modelName].associate(database) | ||
70 | } | ||
71 | }) | ||
72 | |||
73 | if (!silent) logger.info('Database is ready.') | ||
74 | |||
75 | return callback(null) | ||
76 | }) | ||
77 | } | ||
diff --git a/server/initializers/installer.js b/server/initializers/installer.js index 1df300ba8..fb63b81ac 100644 --- a/server/initializers/installer.js +++ b/server/initializers/installer.js | |||
@@ -3,26 +3,27 @@ | |||
3 | const config = require('config') | 3 | const config = require('config') |
4 | const each = require('async/each') | 4 | const each = require('async/each') |
5 | const mkdirp = require('mkdirp') | 5 | const mkdirp = require('mkdirp') |
6 | const mongoose = require('mongoose') | ||
7 | const passwordGenerator = require('password-generator') | 6 | const passwordGenerator = require('password-generator') |
8 | const path = require('path') | 7 | const path = require('path') |
9 | const series = require('async/series') | 8 | const series = require('async/series') |
10 | 9 | ||
11 | const checker = require('./checker') | 10 | const checker = require('./checker') |
12 | const constants = require('./constants') | 11 | const constants = require('./constants') |
12 | const db = require('./database') | ||
13 | const logger = require('../helpers/logger') | 13 | const logger = require('../helpers/logger') |
14 | const peertubeCrypto = require('../helpers/peertube-crypto') | 14 | const peertubeCrypto = require('../helpers/peertube-crypto') |
15 | 15 | ||
16 | const Application = mongoose.model('Application') | ||
17 | const Client = mongoose.model('OAuthClient') | ||
18 | const User = mongoose.model('User') | ||
19 | |||
20 | const installer = { | 16 | const installer = { |
21 | installApplication | 17 | installApplication |
22 | } | 18 | } |
23 | 19 | ||
24 | function installApplication (callback) { | 20 | function installApplication (callback) { |
25 | series([ | 21 | series([ |
22 | function createDatabase (callbackAsync) { | ||
23 | db.sequelize.sync().asCallback(callbackAsync) | ||
24 | // db.sequelize.sync({ force: true }).asCallback(callbackAsync) | ||
25 | }, | ||
26 | |||
26 | function createDirectories (callbackAsync) { | 27 | function createDirectories (callbackAsync) { |
27 | createDirectoriesIfNotExist(callbackAsync) | 28 | createDirectoriesIfNotExist(callbackAsync) |
28 | }, | 29 | }, |
@@ -65,16 +66,18 @@ function createOAuthClientIfNotExist (callback) { | |||
65 | 66 | ||
66 | logger.info('Creating a default OAuth Client.') | 67 | logger.info('Creating a default OAuth Client.') |
67 | 68 | ||
68 | const secret = passwordGenerator(32, false) | 69 | const id = passwordGenerator(32, false, /[a-z0-9]/) |
69 | const client = new Client({ | 70 | const secret = passwordGenerator(32, false, /[a-zA-Z0-9]/) |
71 | const client = db.OAuthClient.build({ | ||
72 | clientId: id, | ||
70 | clientSecret: secret, | 73 | clientSecret: secret, |
71 | grants: [ 'password', 'refresh_token' ] | 74 | grants: [ 'password', 'refresh_token' ] |
72 | }) | 75 | }) |
73 | 76 | ||
74 | client.save(function (err, createdClient) { | 77 | client.save().asCallback(function (err, createdClient) { |
75 | if (err) return callback(err) | 78 | if (err) return callback(err) |
76 | 79 | ||
77 | logger.info('Client id: ' + createdClient._id) | 80 | logger.info('Client id: ' + createdClient.clientId) |
78 | logger.info('Client secret: ' + createdClient.clientSecret) | 81 | logger.info('Client secret: ' + createdClient.clientSecret) |
79 | 82 | ||
80 | return callback(null) | 83 | return callback(null) |
@@ -93,6 +96,7 @@ function createOAuthAdminIfNotExist (callback) { | |||
93 | 96 | ||
94 | const username = 'root' | 97 | const username = 'root' |
95 | const role = constants.USER_ROLES.ADMIN | 98 | const role = constants.USER_ROLES.ADMIN |
99 | const createOptions = {} | ||
96 | let password = '' | 100 | let password = '' |
97 | 101 | ||
98 | // Do not generate a random password for tests | 102 | // Do not generate a random password for tests |
@@ -102,25 +106,27 @@ function createOAuthAdminIfNotExist (callback) { | |||
102 | if (process.env.NODE_APP_INSTANCE) { | 106 | if (process.env.NODE_APP_INSTANCE) { |
103 | password += process.env.NODE_APP_INSTANCE | 107 | password += process.env.NODE_APP_INSTANCE |
104 | } | 108 | } |
109 | |||
110 | // Our password is weak so do not validate it | ||
111 | createOptions.validate = false | ||
105 | } else { | 112 | } else { |
106 | password = passwordGenerator(8, true) | 113 | password = passwordGenerator(8, true) |
107 | } | 114 | } |
108 | 115 | ||
109 | const user = new User({ | 116 | const userData = { |
110 | username, | 117 | username, |
111 | password, | 118 | password, |
112 | role | 119 | role |
113 | }) | 120 | } |
114 | 121 | ||
115 | user.save(function (err, createdUser) { | 122 | db.User.create(userData, createOptions).asCallback(function (err, createdUser) { |
116 | if (err) return callback(err) | 123 | if (err) return callback(err) |
117 | 124 | ||
118 | logger.info('Username: ' + username) | 125 | logger.info('Username: ' + username) |
119 | logger.info('User password: ' + password) | 126 | logger.info('User password: ' + password) |
120 | 127 | ||
121 | logger.info('Creating Application collection.') | 128 | logger.info('Creating Application table.') |
122 | const application = new Application({ mongoSchemaVersion: constants.LAST_MONGO_SCHEMA_VERSION }) | 129 | db.Application.create({ migrationVersion: constants.LAST_MIGRATION_VERSION }).asCallback(callback) |
123 | application.save(callback) | ||
124 | }) | 130 | }) |
125 | }) | 131 | }) |
126 | } | 132 | } |
diff --git a/server/initializers/migrations/0005-create-application.js b/server/initializers/migrations/0005-create-application.js deleted file mode 100644 index e99dec019..000000000 --- a/server/initializers/migrations/0005-create-application.js +++ /dev/null | |||
@@ -1,17 +0,0 @@ | |||
1 | /* | ||
2 | Create the application collection in MongoDB. | ||
3 | Used to store the actual MongoDB scheme version. | ||
4 | */ | ||
5 | |||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const Application = mongoose.model('Application') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | const application = new Application() | ||
12 | application.save(callback) | ||
13 | } | ||
14 | |||
15 | exports.down = function (callback) { | ||
16 | throw new Error('Not implemented.') | ||
17 | } | ||
diff --git a/server/initializers/migrations/0005-example.js b/server/initializers/migrations/0005-example.js new file mode 100644 index 000000000..cedc42919 --- /dev/null +++ b/server/initializers/migrations/0005-example.js | |||
@@ -0,0 +1,14 @@ | |||
1 | // /* | ||
2 | // This is just an example. | ||
3 | // */ | ||
4 | |||
5 | // const db = require('../database') | ||
6 | |||
7 | // // options contains the transaction | ||
8 | // exports.up = function (options, callback) { | ||
9 | // db.Application.create({ migrationVersion: 42 }, { transaction: options.transaction }).asCallback(callback) | ||
10 | // } | ||
11 | |||
12 | // exports.down = function (options, callback) { | ||
13 | // throw new Error('Not implemented.') | ||
14 | // } | ||
diff --git a/server/initializers/migrations/0010-users-password.js b/server/initializers/migrations/0010-users-password.js deleted file mode 100644 index a0616a269..000000000 --- a/server/initializers/migrations/0010-users-password.js +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | /* | ||
2 | Convert plain user password to encrypted user password. | ||
3 | */ | ||
4 | |||
5 | const eachSeries = require('async/eachSeries') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | User.list(function (err, users) { | ||
12 | if (err) return callback(err) | ||
13 | |||
14 | eachSeries(users, function (user, callbackEach) { | ||
15 | user.save(callbackEach) | ||
16 | }, callback) | ||
17 | }) | ||
18 | } | ||
19 | |||
20 | exports.down = function (callback) { | ||
21 | throw new Error('Not implemented.') | ||
22 | } | ||
diff --git a/server/initializers/migrations/0015-admin-role.js b/server/initializers/migrations/0015-admin-role.js deleted file mode 100644 index af06dca9e..000000000 --- a/server/initializers/migrations/0015-admin-role.js +++ /dev/null | |||
@@ -1,16 +0,0 @@ | |||
1 | /* | ||
2 | Set the admin role to the root user. | ||
3 | */ | ||
4 | |||
5 | const constants = require('../constants') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | User.update({ username: 'root' }, { role: constants.USER_ROLES.ADMIN }, callback) | ||
12 | } | ||
13 | |||
14 | exports.down = function (callback) { | ||
15 | throw new Error('Not implemented.') | ||
16 | } | ||
diff --git a/server/initializers/migrations/0020-requests-endpoint.js b/server/initializers/migrations/0020-requests-endpoint.js deleted file mode 100644 index 55feec571..000000000 --- a/server/initializers/migrations/0020-requests-endpoint.js +++ /dev/null | |||
@@ -1,15 +0,0 @@ | |||
1 | /* | ||
2 | Set the endpoint videos for requests. | ||
3 | */ | ||
4 | |||
5 | const mongoose = require('mongoose') | ||
6 | |||
7 | const Request = mongoose.model('Request') | ||
8 | |||
9 | exports.up = function (callback) { | ||
10 | Request.update({ }, { endpoint: 'videos' }, callback) | ||
11 | } | ||
12 | |||
13 | exports.down = function (callback) { | ||
14 | throw new Error('Not implemented.') | ||
15 | } | ||
diff --git a/server/initializers/migrations/0025-video-filenames.js b/server/initializers/migrations/0025-video-filenames.js deleted file mode 100644 index df21494d7..000000000 --- a/server/initializers/migrations/0025-video-filenames.js +++ /dev/null | |||
@@ -1,57 +0,0 @@ | |||
1 | /* | ||
2 | Rename thumbnails and video filenames to _id.extension | ||
3 | */ | ||
4 | |||
5 | const each = require('async/each') | ||
6 | const fs = require('fs') | ||
7 | const path = require('path') | ||
8 | const mongoose = require('mongoose') | ||
9 | |||
10 | const constants = require('../constants') | ||
11 | const logger = require('../../helpers/logger') | ||
12 | |||
13 | const Video = mongoose.model('Video') | ||
14 | |||
15 | exports.up = function (callback) { | ||
16 | // Use of lean because the new Video scheme does not have filename field | ||
17 | Video.find({ filename: { $ne: null } }).lean().exec(function (err, videos) { | ||
18 | if (err) throw err | ||
19 | |||
20 | each(videos, function (video, callbackEach) { | ||
21 | const torrentName = video.filename + '.torrent' | ||
22 | const thumbnailName = video.thumbnail | ||
23 | const thumbnailExtension = path.extname(thumbnailName) | ||
24 | const videoName = video.filename | ||
25 | const videoExtension = path.extname(videoName) | ||
26 | |||
27 | const newTorrentName = video._id + '.torrent' | ||
28 | const newThumbnailName = video._id + thumbnailExtension | ||
29 | const newVideoName = video._id + videoExtension | ||
30 | |||
31 | const torrentsDir = constants.CONFIG.STORAGE.TORRENTS_DIR | ||
32 | const thumbnailsDir = constants.CONFIG.STORAGE.THUMBNAILS_DIR | ||
33 | const videosDir = constants.CONFIG.STORAGE.VIDEOS_DIR | ||
34 | |||
35 | logger.info('Renaming %s to %s.', torrentsDir + torrentName, torrentsDir + newTorrentName) | ||
36 | fs.renameSync(torrentsDir + torrentName, torrentsDir + newTorrentName) | ||
37 | |||
38 | logger.info('Renaming %s to %s.', thumbnailsDir + thumbnailName, thumbnailsDir + newThumbnailName) | ||
39 | fs.renameSync(thumbnailsDir + thumbnailName, thumbnailsDir + newThumbnailName) | ||
40 | |||
41 | logger.info('Renaming %s to %s.', videosDir + videoName, videosDir + newVideoName) | ||
42 | fs.renameSync(videosDir + videoName, videosDir + newVideoName) | ||
43 | |||
44 | Video.load(video._id, function (err, videoObj) { | ||
45 | if (err) return callbackEach(err) | ||
46 | |||
47 | videoObj.extname = videoExtension | ||
48 | videoObj.remoteId = null | ||
49 | videoObj.save(callbackEach) | ||
50 | }) | ||
51 | }, callback) | ||
52 | }) | ||
53 | } | ||
54 | |||
55 | exports.down = function (callback) { | ||
56 | throw new Error('Not implemented.') | ||
57 | } | ||
diff --git a/server/initializers/migrations/0030-video-magnet.js b/server/initializers/migrations/0030-video-magnet.js deleted file mode 100644 index b9119d61c..000000000 --- a/server/initializers/migrations/0030-video-magnet.js +++ /dev/null | |||
@@ -1,32 +0,0 @@ | |||
1 | /* | ||
2 | Change video magnet structures | ||
3 | */ | ||
4 | |||
5 | const each = require('async/each') | ||
6 | const magnet = require('magnet-uri') | ||
7 | const mongoose = require('mongoose') | ||
8 | |||
9 | const Video = mongoose.model('Video') | ||
10 | |||
11 | exports.up = function (callback) { | ||
12 | // Use of lean because the new Video scheme does not have magnetUri field | ||
13 | Video.find({ }).lean().exec(function (err, videos) { | ||
14 | if (err) throw err | ||
15 | |||
16 | each(videos, function (video, callbackEach) { | ||
17 | const parsed = magnet.decode(video.magnetUri) | ||
18 | const infoHash = parsed.infoHash | ||
19 | |||
20 | Video.load(video._id, function (err, videoObj) { | ||
21 | if (err) return callbackEach(err) | ||
22 | |||
23 | videoObj.magnet.infoHash = infoHash | ||
24 | videoObj.save(callbackEach) | ||
25 | }) | ||
26 | }, callback) | ||
27 | }) | ||
28 | } | ||
29 | |||
30 | exports.down = function (callback) { | ||
31 | throw new Error('Not implemented.') | ||
32 | } | ||
diff --git a/server/initializers/migrations/0035-url-to-host.js b/server/initializers/migrations/0035-url-to-host.js deleted file mode 100644 index 6243304d5..000000000 --- a/server/initializers/migrations/0035-url-to-host.js +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | /* | ||
2 | Change video magnet structures | ||
3 | */ | ||
4 | |||
5 | const each = require('async/each') | ||
6 | const mongoose = require('mongoose') | ||
7 | |||
8 | const Video = mongoose.model('Video') | ||
9 | |||
10 | exports.up = function (callback) { | ||
11 | // Use of lean because the new Video scheme does not have podUrl field | ||
12 | Video.find({ }).lean().exec(function (err, videos) { | ||
13 | if (err) throw err | ||
14 | |||
15 | each(videos, function (video, callbackEach) { | ||
16 | Video.load(video._id, function (err, videoObj) { | ||
17 | if (err) return callbackEach(err) | ||
18 | |||
19 | const host = video.podUrl.split('://')[1] | ||
20 | |||
21 | videoObj.podHost = host | ||
22 | videoObj.save(callbackEach) | ||
23 | }) | ||
24 | }, callback) | ||
25 | }) | ||
26 | } | ||
27 | |||
28 | exports.down = function (callback) { | ||
29 | throw new Error('Not implemented.') | ||
30 | } | ||
diff --git a/server/initializers/migrations/0040-video-remote-id.js b/server/initializers/migrations/0040-video-remote-id.js deleted file mode 100644 index 46a14a689..000000000 --- a/server/initializers/migrations/0040-video-remote-id.js +++ /dev/null | |||
@@ -1,59 +0,0 @@ | |||
1 | /* | ||
2 | Use remote id as identifier | ||
3 | */ | ||
4 | |||
5 | const map = require('lodash/map') | ||
6 | const mongoose = require('mongoose') | ||
7 | const readline = require('readline') | ||
8 | |||
9 | const rl = readline.createInterface({ | ||
10 | input: process.stdin, | ||
11 | output: process.stdout | ||
12 | }) | ||
13 | |||
14 | const logger = require('../../helpers/logger') | ||
15 | const friends = require('../../lib/friends') | ||
16 | |||
17 | const Pod = mongoose.model('Pod') | ||
18 | const Video = mongoose.model('Video') | ||
19 | |||
20 | exports.up = function (callback) { | ||
21 | Pod.find({}).lean().exec(function (err, pods) { | ||
22 | if (err) return callback(err) | ||
23 | |||
24 | // We need to quit friends first | ||
25 | if (pods.length === 0) { | ||
26 | return setVideosRemoteId(callback) | ||
27 | } | ||
28 | |||
29 | const timeout = setTimeout(function () { | ||
30 | throw new Error('You need to enter a value!') | ||
31 | }, 10000) | ||
32 | |||
33 | rl.question('I am sorry but I need to quit friends for upgrading. Do you want to continue? (yes/*)', function (answer) { | ||
34 | if (answer !== 'yes') throw new Error('I cannot continue.') | ||
35 | |||
36 | clearTimeout(timeout) | ||
37 | rl.close() | ||
38 | |||
39 | const urls = map(pods, 'url') | ||
40 | logger.info('Saying goodbye to: ' + urls.join(', ')) | ||
41 | |||
42 | setVideosRemoteId(function () { | ||
43 | friends.quitFriends(callback) | ||
44 | }) | ||
45 | }) | ||
46 | }) | ||
47 | } | ||
48 | |||
49 | exports.down = function (callback) { | ||
50 | throw new Error('Not implemented.') | ||
51 | } | ||
52 | |||
53 | function setVideosRemoteId (callback) { | ||
54 | Video.update({ filename: { $ne: null } }, { remoteId: null }, function (err) { | ||
55 | if (err) throw err | ||
56 | |||
57 | Video.update({ filename: null }, { remoteId: mongoose.Types.ObjectId() }, callback) | ||
58 | }) | ||
59 | } | ||
diff --git a/server/initializers/migrator.js b/server/initializers/migrator.js index 6b31d994f..e5288b615 100644 --- a/server/initializers/migrator.js +++ b/server/initializers/migrator.js | |||
@@ -1,48 +1,36 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const eachSeries = require('async/eachSeries') | 3 | const eachSeries = require('async/eachSeries') |
4 | const mongoose = require('mongoose') | 4 | const fs = require('fs') |
5 | const path = require('path') | 5 | const path = require('path') |
6 | 6 | ||
7 | const constants = require('./constants') | 7 | const constants = require('./constants') |
8 | const db = require('./database') | ||
8 | const logger = require('../helpers/logger') | 9 | const logger = require('../helpers/logger') |
9 | 10 | ||
10 | const Application = mongoose.model('Application') | ||
11 | |||
12 | const migrator = { | 11 | const migrator = { |
13 | migrate: migrate | 12 | migrate: migrate |
14 | } | 13 | } |
15 | 14 | ||
16 | function migrate (callback) { | 15 | function migrate (callback) { |
17 | Application.loadMongoSchemaVersion(function (err, actualVersion) { | 16 | db.Application.loadMigrationVersion(function (err, actualVersion) { |
18 | if (err) return callback(err) | 17 | if (err) return callback(err) |
19 | 18 | ||
20 | // If there are a new mongo schemas | 19 | // If there are a new migration scripts |
21 | if (!actualVersion || actualVersion < constants.LAST_MONGO_SCHEMA_VERSION) { | 20 | if (actualVersion < constants.LAST_MIGRATION_VERSION) { |
22 | logger.info('Begin migrations.') | 21 | logger.info('Begin migrations.') |
23 | 22 | ||
24 | eachSeries(constants.MONGO_MIGRATION_SCRIPTS, function (entity, callbackEach) { | 23 | getMigrationScripts(function (err, migrationScripts) { |
25 | const versionScript = entity.version | 24 | if (err) return callback(err) |
26 | |||
27 | // Do not execute old migration scripts | ||
28 | if (versionScript <= actualVersion) return callbackEach(null) | ||
29 | |||
30 | // Load the migration module and run it | ||
31 | const migrationScriptName = entity.script | ||
32 | logger.info('Executing %s migration script.', migrationScriptName) | ||
33 | 25 | ||
34 | const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) | 26 | eachSeries(migrationScripts, function (entity, callbackEach) { |
35 | migrationScript.up(function (err) { | 27 | executeMigration(actualVersion, entity, callbackEach) |
36 | if (err) return callbackEach(err) | 28 | }, function (err) { |
29 | if (err) return callback(err) | ||
37 | 30 | ||
38 | // Update the new mongo version schema | 31 | logger.info('Migrations finished. New migration version schema: %s', constants.LAST_MIGRATION_VERSION) |
39 | Application.updateMongoSchemaVersion(versionScript, callbackEach) | 32 | return callback(null) |
40 | }) | 33 | }) |
41 | }, function (err) { | ||
42 | if (err) return callback(err) | ||
43 | |||
44 | logger.info('Migrations finished. New mongo version schema: %s', constants.LAST_MONGO_SCHEMA_VERSION) | ||
45 | return callback(null) | ||
46 | }) | 34 | }) |
47 | } else { | 35 | } else { |
48 | return callback(null) | 36 | return callback(null) |
@@ -54,3 +42,57 @@ function migrate (callback) { | |||
54 | 42 | ||
55 | module.exports = migrator | 43 | module.exports = migrator |
56 | 44 | ||
45 | // --------------------------------------------------------------------------- | ||
46 | |||
47 | function getMigrationScripts (callback) { | ||
48 | fs.readdir(path.join(__dirname, 'migrations'), function (err, files) { | ||
49 | if (err) return callback(err) | ||
50 | |||
51 | const filesToMigrate = [] | ||
52 | |||
53 | files.forEach(function (file) { | ||
54 | // Filename is something like 'version-blabla.js' | ||
55 | const version = file.split('-')[0] | ||
56 | filesToMigrate.push({ | ||
57 | version, | ||
58 | script: file | ||
59 | }) | ||
60 | }) | ||
61 | |||
62 | return callback(err, filesToMigrate) | ||
63 | }) | ||
64 | } | ||
65 | |||
66 | function executeMigration (actualVersion, entity, callback) { | ||
67 | const versionScript = entity.version | ||
68 | |||
69 | // Do not execute old migration scripts | ||
70 | if (versionScript <= actualVersion) return callback(null) | ||
71 | |||
72 | // Load the migration module and run it | ||
73 | const migrationScriptName = entity.script | ||
74 | logger.info('Executing %s migration script.', migrationScriptName) | ||
75 | |||
76 | const migrationScript = require(path.join(__dirname, 'migrations', migrationScriptName)) | ||
77 | |||
78 | db.sequelize.transaction().asCallback(function (err, t) { | ||
79 | if (err) return callback(err) | ||
80 | |||
81 | migrationScript.up({ transaction: t }, function (err) { | ||
82 | if (err) { | ||
83 | t.rollback() | ||
84 | return callback(err) | ||
85 | } | ||
86 | |||
87 | // Update the new migration version | ||
88 | db.Application.updateMigrationVersion(versionScript, t, function (err) { | ||
89 | if (err) { | ||
90 | t.rollback() | ||
91 | return callback(err) | ||
92 | } | ||
93 | |||
94 | t.commit().asCallback(callback) | ||
95 | }) | ||
96 | }) | ||
97 | }) | ||
98 | } | ||
diff --git a/server/lib/friends.js b/server/lib/friends.js index eaea040ca..f0575ff2f 100644 --- a/server/lib/friends.js +++ b/server/lib/friends.js | |||
@@ -4,20 +4,18 @@ const each = require('async/each') | |||
4 | const eachLimit = require('async/eachLimit') | 4 | const eachLimit = require('async/eachLimit') |
5 | const eachSeries = require('async/eachSeries') | 5 | const eachSeries = require('async/eachSeries') |
6 | const fs = require('fs') | 6 | const fs = require('fs') |
7 | const mongoose = require('mongoose') | ||
8 | const request = require('request') | 7 | const request = require('request') |
9 | const waterfall = require('async/waterfall') | 8 | const waterfall = require('async/waterfall') |
10 | 9 | ||
11 | const constants = require('../initializers/constants') | 10 | const constants = require('../initializers/constants') |
11 | const db = require('../initializers/database') | ||
12 | const logger = require('../helpers/logger') | 12 | const logger = require('../helpers/logger') |
13 | const requests = require('../helpers/requests') | 13 | const requests = require('../helpers/requests') |
14 | 14 | ||
15 | const Pod = mongoose.model('Pod') | ||
16 | const Request = mongoose.model('Request') | ||
17 | const Video = mongoose.model('Video') | ||
18 | |||
19 | const friends = { | 15 | const friends = { |
20 | addVideoToFriends, | 16 | addVideoToFriends, |
17 | updateVideoToFriends, | ||
18 | reportAbuseVideoToFriend, | ||
21 | hasFriends, | 19 | hasFriends, |
22 | getMyCertificate, | 20 | getMyCertificate, |
23 | makeFriends, | 21 | makeFriends, |
@@ -26,12 +24,47 @@ const friends = { | |||
26 | sendOwnedVideosToPod | 24 | sendOwnedVideosToPod |
27 | } | 25 | } |
28 | 26 | ||
29 | function addVideoToFriends (video) { | 27 | function addVideoToFriends (videoData, transaction, callback) { |
30 | createRequest('add', constants.REQUEST_ENDPOINTS.VIDEOS, video) | 28 | const options = { |
29 | type: 'add', | ||
30 | endpoint: constants.REQUEST_ENDPOINTS.VIDEOS, | ||
31 | data: videoData, | ||
32 | transaction | ||
33 | } | ||
34 | createRequest(options, callback) | ||
35 | } | ||
36 | |||
37 | function updateVideoToFriends (videoData, transaction, callback) { | ||
38 | const options = { | ||
39 | type: 'update', | ||
40 | endpoint: constants.REQUEST_ENDPOINTS.VIDEOS, | ||
41 | data: videoData, | ||
42 | transaction | ||
43 | } | ||
44 | createRequest(options, callback) | ||
45 | } | ||
46 | |||
47 | function removeVideoToFriends (videoParams) { | ||
48 | const options = { | ||
49 | type: 'remove', | ||
50 | endpoint: constants.REQUEST_ENDPOINTS.VIDEOS, | ||
51 | data: videoParams | ||
52 | } | ||
53 | createRequest(options) | ||
54 | } | ||
55 | |||
56 | function reportAbuseVideoToFriend (reportData, video) { | ||
57 | const options = { | ||
58 | type: 'report-abuse', | ||
59 | endpoint: constants.REQUEST_ENDPOINTS.VIDEOS, | ||
60 | data: reportData, | ||
61 | toIds: [ video.Author.podId ] | ||
62 | } | ||
63 | createRequest(options) | ||
31 | } | 64 | } |
32 | 65 | ||
33 | function hasFriends (callback) { | 66 | function hasFriends (callback) { |
34 | Pod.countAll(function (err, count) { | 67 | db.Pod.countAll(function (err, count) { |
35 | if (err) return callback(err) | 68 | if (err) return callback(err) |
36 | 69 | ||
37 | const hasFriends = (count !== 0) | 70 | const hasFriends = (count !== 0) |
@@ -69,13 +102,15 @@ function makeFriends (hosts, callback) { | |||
69 | 102 | ||
70 | function quitFriends (callback) { | 103 | function quitFriends (callback) { |
71 | // Stop pool requests | 104 | // Stop pool requests |
72 | Request.deactivate() | 105 | db.Request.deactivate() |
73 | // Flush pool requests | ||
74 | Request.flush() | ||
75 | 106 | ||
76 | waterfall([ | 107 | waterfall([ |
108 | function flushRequests (callbackAsync) { | ||
109 | db.Request.flush(callbackAsync) | ||
110 | }, | ||
111 | |||
77 | function getPodsList (callbackAsync) { | 112 | function getPodsList (callbackAsync) { |
78 | return Pod.list(callbackAsync) | 113 | return db.Pod.list(callbackAsync) |
79 | }, | 114 | }, |
80 | 115 | ||
81 | function announceIQuitMyFriends (pods, callbackAsync) { | 116 | function announceIQuitMyFriends (pods, callbackAsync) { |
@@ -103,12 +138,12 @@ function quitFriends (callback) { | |||
103 | 138 | ||
104 | function removePodsFromDB (pods, callbackAsync) { | 139 | function removePodsFromDB (pods, callbackAsync) { |
105 | each(pods, function (pod, callbackEach) { | 140 | each(pods, function (pod, callbackEach) { |
106 | pod.remove(callbackEach) | 141 | pod.destroy().asCallback(callbackEach) |
107 | }, callbackAsync) | 142 | }, callbackAsync) |
108 | } | 143 | } |
109 | ], function (err) { | 144 | ], function (err) { |
110 | // Don't forget to re activate the scheduler, even if there was an error | 145 | // Don't forget to re activate the scheduler, even if there was an error |
111 | Request.activate() | 146 | db.Request.activate() |
112 | 147 | ||
113 | if (err) return callback(err) | 148 | if (err) return callback(err) |
114 | 149 | ||
@@ -117,26 +152,28 @@ function quitFriends (callback) { | |||
117 | }) | 152 | }) |
118 | } | 153 | } |
119 | 154 | ||
120 | function removeVideoToFriends (videoParams) { | ||
121 | createRequest('remove', constants.REQUEST_ENDPOINTS.VIDEOS, videoParams) | ||
122 | } | ||
123 | |||
124 | function sendOwnedVideosToPod (podId) { | 155 | function sendOwnedVideosToPod (podId) { |
125 | Video.listOwned(function (err, videosList) { | 156 | db.Video.listOwnedAndPopulateAuthorAndTags(function (err, videosList) { |
126 | if (err) { | 157 | if (err) { |
127 | logger.error('Cannot get the list of videos we own.') | 158 | logger.error('Cannot get the list of videos we own.') |
128 | return | 159 | return |
129 | } | 160 | } |
130 | 161 | ||
131 | videosList.forEach(function (video) { | 162 | videosList.forEach(function (video) { |
132 | video.toRemoteJSON(function (err, remoteVideo) { | 163 | video.toAddRemoteJSON(function (err, remoteVideo) { |
133 | if (err) { | 164 | if (err) { |
134 | logger.error('Cannot convert video to remote.', { error: err }) | 165 | logger.error('Cannot convert video to remote.', { error: err }) |
135 | // Don't break the process | 166 | // Don't break the process |
136 | return | 167 | return |
137 | } | 168 | } |
138 | 169 | ||
139 | createRequest('add', constants.REQUEST_ENDPOINTS.VIDEOS, remoteVideo, [ podId ]) | 170 | const options = { |
171 | type: 'add', | ||
172 | endpoint: constants.REQUEST_ENDPOINTS.VIDEOS, | ||
173 | data: remoteVideo, | ||
174 | toIds: [ podId ] | ||
175 | } | ||
176 | createRequest(options) | ||
140 | }) | 177 | }) |
141 | }) | 178 | }) |
142 | }) | 179 | }) |
@@ -149,10 +186,10 @@ module.exports = friends | |||
149 | // --------------------------------------------------------------------------- | 186 | // --------------------------------------------------------------------------- |
150 | 187 | ||
151 | function computeForeignPodsList (host, podsScore, callback) { | 188 | function computeForeignPodsList (host, podsScore, callback) { |
152 | getForeignPodsList(host, function (err, foreignPodsList) { | 189 | getForeignPodsList(host, function (err, res) { |
153 | if (err) return callback(err) | 190 | if (err) return callback(err) |
154 | 191 | ||
155 | if (!foreignPodsList) foreignPodsList = [] | 192 | const foreignPodsList = res.data |
156 | 193 | ||
157 | // Let's give 1 point to the pod we ask the friends list | 194 | // Let's give 1 point to the pod we ask the friends list |
158 | foreignPodsList.push({ host }) | 195 | foreignPodsList.push({ host }) |
@@ -200,9 +237,9 @@ function getForeignPodsList (host, callback) { | |||
200 | 237 | ||
201 | function makeRequestsToWinningPods (cert, podsList, callback) { | 238 | function makeRequestsToWinningPods (cert, podsList, callback) { |
202 | // Stop pool requests | 239 | // Stop pool requests |
203 | Request.deactivate() | 240 | db.Request.deactivate() |
204 | // Flush pool requests | 241 | // Flush pool requests |
205 | Request.forceSend() | 242 | db.Request.forceSend() |
206 | 243 | ||
207 | eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) { | 244 | eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) { |
208 | const params = { | 245 | const params = { |
@@ -222,15 +259,15 @@ function makeRequestsToWinningPods (cert, podsList, callback) { | |||
222 | } | 259 | } |
223 | 260 | ||
224 | if (res.statusCode === 200) { | 261 | if (res.statusCode === 200) { |
225 | const podObj = new Pod({ host: pod.host, publicKey: body.cert }) | 262 | const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert }) |
226 | podObj.save(function (err, podCreated) { | 263 | podObj.save().asCallback(function (err, podCreated) { |
227 | if (err) { | 264 | if (err) { |
228 | logger.error('Cannot add friend %s pod.', pod.host, { error: err }) | 265 | logger.error('Cannot add friend %s pod.', pod.host, { error: err }) |
229 | return callbackEach() | 266 | return callbackEach() |
230 | } | 267 | } |
231 | 268 | ||
232 | // Add our videos to the request scheduler | 269 | // Add our videos to the request scheduler |
233 | sendOwnedVideosToPod(podCreated._id) | 270 | sendOwnedVideosToPod(podCreated.id) |
234 | 271 | ||
235 | return callbackEach() | 272 | return callbackEach() |
236 | }) | 273 | }) |
@@ -242,28 +279,64 @@ function makeRequestsToWinningPods (cert, podsList, callback) { | |||
242 | }, function endRequests () { | 279 | }, function endRequests () { |
243 | // Final callback, we've ended all the requests | 280 | // Final callback, we've ended all the requests |
244 | // Now we made new friends, we can re activate the pool of requests | 281 | // Now we made new friends, we can re activate the pool of requests |
245 | Request.activate() | 282 | db.Request.activate() |
246 | 283 | ||
247 | logger.debug('makeRequestsToWinningPods finished.') | 284 | logger.debug('makeRequestsToWinningPods finished.') |
248 | return callback() | 285 | return callback() |
249 | }) | 286 | }) |
250 | } | 287 | } |
251 | 288 | ||
252 | function createRequest (type, endpoint, data, to) { | 289 | // Wrapper that populate "toIds" argument with all our friends if it is not specified |
253 | const req = new Request({ | 290 | // { type, endpoint, data, toIds, transaction } |
291 | function createRequest (options, callback) { | ||
292 | if (!callback) callback = function () {} | ||
293 | if (options.toIds) return _createRequest(options, callback) | ||
294 | |||
295 | // If the "toIds" pods is not specified, we send the request to all our friends | ||
296 | db.Pod.listAllIds(options.transaction, function (err, podIds) { | ||
297 | if (err) { | ||
298 | logger.error('Cannot get pod ids', { error: err }) | ||
299 | return | ||
300 | } | ||
301 | |||
302 | const newOptions = Object.assign(options, { toIds: podIds }) | ||
303 | return _createRequest(newOptions, callback) | ||
304 | }) | ||
305 | } | ||
306 | |||
307 | // { type, endpoint, data, toIds, transaction } | ||
308 | function _createRequest (options, callback) { | ||
309 | const type = options.type | ||
310 | const endpoint = options.endpoint | ||
311 | const data = options.data | ||
312 | const toIds = options.toIds | ||
313 | const transaction = options.transaction | ||
314 | |||
315 | const pods = [] | ||
316 | |||
317 | // If there are no destination pods abort | ||
318 | if (toIds.length === 0) return callback(null) | ||
319 | |||
320 | toIds.forEach(function (toPod) { | ||
321 | pods.push(db.Pod.build({ id: toPod })) | ||
322 | }) | ||
323 | |||
324 | const createQuery = { | ||
254 | endpoint, | 325 | endpoint, |
255 | request: { | 326 | request: { |
256 | type: type, | 327 | type: type, |
257 | data: data | 328 | data: data |
258 | } | 329 | } |
259 | }) | 330 | } |
260 | 331 | ||
261 | if (to) { | 332 | const dbRequestOptions = { |
262 | req.to = to | 333 | transaction |
263 | } | 334 | } |
264 | 335 | ||
265 | req.save(function (err) { | 336 | return db.Request.create(createQuery, dbRequestOptions).asCallback(function (err, request) { |
266 | if (err) logger.error('Cannot save the request.', { error: err }) | 337 | if (err) return callback(err) |
338 | |||
339 | return request.setPods(pods, dbRequestOptions).asCallback(callback) | ||
267 | }) | 340 | }) |
268 | } | 341 | } |
269 | 342 | ||
diff --git a/server/lib/oauth-model.js b/server/lib/oauth-model.js index d011c4b72..1c12f1b14 100644 --- a/server/lib/oauth-model.js +++ b/server/lib/oauth-model.js | |||
@@ -1,11 +1,6 @@ | |||
1 | const mongoose = require('mongoose') | 1 | const db = require('../initializers/database') |
2 | |||
3 | const logger = require('../helpers/logger') | 2 | const logger = require('../helpers/logger') |
4 | 3 | ||
5 | const OAuthClient = mongoose.model('OAuthClient') | ||
6 | const OAuthToken = mongoose.model('OAuthToken') | ||
7 | const User = mongoose.model('User') | ||
8 | |||
9 | // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications | 4 | // See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications |
10 | const OAuthModel = { | 5 | const OAuthModel = { |
11 | getAccessToken, | 6 | getAccessToken, |
@@ -21,27 +16,25 @@ const OAuthModel = { | |||
21 | function getAccessToken (bearerToken) { | 16 | function getAccessToken (bearerToken) { |
22 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') | 17 | logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') |
23 | 18 | ||
24 | return OAuthToken.getByTokenAndPopulateUser(bearerToken) | 19 | return db.OAuthToken.getByTokenAndPopulateUser(bearerToken) |
25 | } | 20 | } |
26 | 21 | ||
27 | function getClient (clientId, clientSecret) { | 22 | function getClient (clientId, clientSecret) { |
28 | logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').') | 23 | logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').') |
29 | 24 | ||
30 | // TODO req validator | 25 | return db.OAuthClient.getByIdAndSecret(clientId, clientSecret) |
31 | const mongoId = new mongoose.mongo.ObjectID(clientId) | ||
32 | return OAuthClient.getByIdAndSecret(mongoId, clientSecret) | ||
33 | } | 26 | } |
34 | 27 | ||
35 | function getRefreshToken (refreshToken) { | 28 | function getRefreshToken (refreshToken) { |
36 | logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') | 29 | logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') |
37 | 30 | ||
38 | return OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken) | 31 | return db.OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken) |
39 | } | 32 | } |
40 | 33 | ||
41 | function getUser (username, password) { | 34 | function getUser (username, password) { |
42 | logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') | 35 | logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') |
43 | 36 | ||
44 | return User.getByUsername(username).then(function (user) { | 37 | return db.User.getByUsername(username).then(function (user) { |
45 | if (!user) return null | 38 | if (!user) return null |
46 | 39 | ||
47 | // We need to return a promise | 40 | // We need to return a promise |
@@ -60,8 +53,8 @@ function getUser (username, password) { | |||
60 | } | 53 | } |
61 | 54 | ||
62 | function revokeToken (token) { | 55 | function revokeToken (token) { |
63 | return OAuthToken.getByRefreshTokenAndPopulateUser(token.refreshToken).then(function (tokenDB) { | 56 | return db.OAuthToken.getByRefreshTokenAndPopulateUser(token.refreshToken).then(function (tokenDB) { |
64 | if (tokenDB) tokenDB.remove() | 57 | if (tokenDB) tokenDB.destroy() |
65 | 58 | ||
66 | /* | 59 | /* |
67 | * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js | 60 | * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js |
@@ -80,18 +73,19 @@ function revokeToken (token) { | |||
80 | function saveToken (token, client, user) { | 73 | function saveToken (token, client, user) { |
81 | logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') | 74 | logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') |
82 | 75 | ||
83 | const tokenObj = new OAuthToken({ | 76 | const tokenToCreate = { |
84 | accessToken: token.accessToken, | 77 | accessToken: token.accessToken, |
85 | accessTokenExpiresAt: token.accessTokenExpiresAt, | 78 | accessTokenExpiresAt: token.accessTokenExpiresAt, |
86 | client: client.id, | ||
87 | refreshToken: token.refreshToken, | 79 | refreshToken: token.refreshToken, |
88 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, | 80 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, |
89 | user: user.id | 81 | oAuthClientId: client.id, |
90 | }) | 82 | userId: user.id |
83 | } | ||
91 | 84 | ||
92 | return tokenObj.save().then(function (tokenCreated) { | 85 | return db.OAuthToken.create(tokenToCreate).then(function (tokenCreated) { |
93 | tokenCreated.client = client | 86 | tokenCreated.client = client |
94 | tokenCreated.user = user | 87 | tokenCreated.user = user |
88 | |||
95 | return tokenCreated | 89 | return tokenCreated |
96 | }).catch(function (err) { | 90 | }).catch(function (err) { |
97 | throw err | 91 | throw err |
diff --git a/server/middlewares/pods.js b/server/middlewares/pods.js index 487ea1259..e38fb341d 100644 --- a/server/middlewares/pods.js +++ b/server/middlewares/pods.js | |||
@@ -44,7 +44,6 @@ module.exports = podsMiddleware | |||
44 | function getHostWithPort (host) { | 44 | function getHostWithPort (host) { |
45 | const splitted = host.split(':') | 45 | const splitted = host.split(':') |
46 | 46 | ||
47 | console.log(splitted) | ||
48 | // The port was not specified | 47 | // The port was not specified |
49 | if (splitted.length === 1) { | 48 | if (splitted.length === 1) { |
50 | if (constants.REMOTE_SCHEME.HTTP === 'https') return host + ':443' | 49 | if (constants.REMOTE_SCHEME.HTTP === 'https') return host + ':443' |
diff --git a/server/middlewares/secure.js b/server/middlewares/secure.js index ee836beed..b6e6d818b 100644 --- a/server/middlewares/secure.js +++ b/server/middlewares/secure.js | |||
@@ -1,18 +1,16 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const db = require('../initializers/database') | ||
3 | const logger = require('../helpers/logger') | 4 | const logger = require('../helpers/logger') |
4 | const mongoose = require('mongoose') | ||
5 | const peertubeCrypto = require('../helpers/peertube-crypto') | 5 | const peertubeCrypto = require('../helpers/peertube-crypto') |
6 | 6 | ||
7 | const Pod = mongoose.model('Pod') | ||
8 | |||
9 | const secureMiddleware = { | 7 | const secureMiddleware = { |
10 | checkSignature | 8 | checkSignature |
11 | } | 9 | } |
12 | 10 | ||
13 | function checkSignature (req, res, next) { | 11 | function checkSignature (req, res, next) { |
14 | const host = req.body.signature.host | 12 | const host = req.body.signature.host |
15 | Pod.loadByHost(host, function (err, pod) { | 13 | db.Pod.loadByHost(host, function (err, pod) { |
16 | if (err) { | 14 | if (err) { |
17 | logger.error('Cannot get signed host in body.', { error: err }) | 15 | logger.error('Cannot get signed host in body.', { error: err }) |
18 | return res.sendStatus(500) | 16 | return res.sendStatus(500) |
@@ -25,9 +23,20 @@ function checkSignature (req, res, next) { | |||
25 | 23 | ||
26 | logger.debug('Checking signature from %s.', host) | 24 | logger.debug('Checking signature from %s.', host) |
27 | 25 | ||
28 | const signatureOk = peertubeCrypto.checkSignature(pod.publicKey, host, req.body.signature.signature) | 26 | let signatureShouldBe |
27 | if (req.body.data) { | ||
28 | signatureShouldBe = req.body.data | ||
29 | } else { | ||
30 | signatureShouldBe = host | ||
31 | } | ||
32 | |||
33 | const signatureOk = peertubeCrypto.checkSignature(pod.publicKey, signatureShouldBe, req.body.signature.signature) | ||
29 | 34 | ||
30 | if (signatureOk === true) { | 35 | if (signatureOk === true) { |
36 | res.locals.secure = { | ||
37 | pod | ||
38 | } | ||
39 | |||
31 | return next() | 40 | return next() |
32 | } | 41 | } |
33 | 42 | ||
diff --git a/server/middlewares/sort.js b/server/middlewares/sort.js index f0b7274eb..39e167265 100644 --- a/server/middlewares/sort.js +++ b/server/middlewares/sort.js | |||
@@ -2,17 +2,24 @@ | |||
2 | 2 | ||
3 | const sortMiddleware = { | 3 | const sortMiddleware = { |
4 | setUsersSort, | 4 | setUsersSort, |
5 | setVideoAbusesSort, | ||
5 | setVideosSort | 6 | setVideosSort |
6 | } | 7 | } |
7 | 8 | ||
8 | function setUsersSort (req, res, next) { | 9 | function setUsersSort (req, res, next) { |
9 | if (!req.query.sort) req.query.sort = '-createdDate' | 10 | if (!req.query.sort) req.query.sort = '-createdAt' |
11 | |||
12 | return next() | ||
13 | } | ||
14 | |||
15 | function setVideoAbusesSort (req, res, next) { | ||
16 | if (!req.query.sort) req.query.sort = '-createdAt' | ||
10 | 17 | ||
11 | return next() | 18 | return next() |
12 | } | 19 | } |
13 | 20 | ||
14 | function setVideosSort (req, res, next) { | 21 | function setVideosSort (req, res, next) { |
15 | if (!req.query.sort) req.query.sort = '-createdDate' | 22 | if (!req.query.sort) req.query.sort = '-createdAt' |
16 | 23 | ||
17 | return next() | 24 | return next() |
18 | } | 25 | } |
diff --git a/server/middlewares/validators/remote.js b/server/middlewares/validators/remote.js deleted file mode 100644 index 858d193cc..000000000 --- a/server/middlewares/validators/remote.js +++ /dev/null | |||
@@ -1,30 +0,0 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const checkErrors = require('./utils').checkErrors | ||
4 | const logger = require('../../helpers/logger') | ||
5 | |||
6 | const validatorsRemote = { | ||
7 | remoteVideos, | ||
8 | signature | ||
9 | } | ||
10 | |||
11 | function remoteVideos (req, res, next) { | ||
12 | req.checkBody('data').isEachRemoteVideosValid() | ||
13 | |||
14 | logger.debug('Checking remoteVideos parameters', { parameters: req.body }) | ||
15 | |||
16 | checkErrors(req, res, next) | ||
17 | } | ||
18 | |||
19 | function signature (req, res, next) { | ||
20 | req.checkBody('signature.host', 'Should have a signature host').isURL() | ||
21 | req.checkBody('signature.signature', 'Should have a signature').notEmpty() | ||
22 | |||
23 | logger.debug('Checking signature parameters', { parameters: { signatureHost: req.body.signature.host } }) | ||
24 | |||
25 | checkErrors(req, res, next) | ||
26 | } | ||
27 | |||
28 | // --------------------------------------------------------------------------- | ||
29 | |||
30 | module.exports = validatorsRemote | ||
diff --git a/server/middlewares/validators/remote/index.js b/server/middlewares/validators/remote/index.js new file mode 100644 index 000000000..022a2fe50 --- /dev/null +++ b/server/middlewares/validators/remote/index.js | |||
@@ -0,0 +1,13 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const remoteSignatureValidators = require('./signature') | ||
4 | const remoteVideosValidators = require('./videos') | ||
5 | |||
6 | const validators = { | ||
7 | signature: remoteSignatureValidators, | ||
8 | videos: remoteVideosValidators | ||
9 | } | ||
10 | |||
11 | // --------------------------------------------------------------------------- | ||
12 | |||
13 | module.exports = validators | ||
diff --git a/server/middlewares/validators/remote/signature.js b/server/middlewares/validators/remote/signature.js new file mode 100644 index 000000000..002232c05 --- /dev/null +++ b/server/middlewares/validators/remote/signature.js | |||
@@ -0,0 +1,21 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const checkErrors = require('../utils').checkErrors | ||
4 | const logger = require('../../../helpers/logger') | ||
5 | |||
6 | const validatorsRemoteSignature = { | ||
7 | signature | ||
8 | } | ||
9 | |||
10 | function signature (req, res, next) { | ||
11 | req.checkBody('signature.host', 'Should have a signature host').isURL() | ||
12 | req.checkBody('signature.signature', 'Should have a signature').notEmpty() | ||
13 | |||
14 | logger.debug('Checking signature parameters', { parameters: { signature: req.body.signature } }) | ||
15 | |||
16 | checkErrors(req, res, next) | ||
17 | } | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | module.exports = validatorsRemoteSignature | ||
diff --git a/server/middlewares/validators/remote/videos.js b/server/middlewares/validators/remote/videos.js new file mode 100644 index 000000000..cf9925b6c --- /dev/null +++ b/server/middlewares/validators/remote/videos.js | |||
@@ -0,0 +1,20 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const checkErrors = require('../utils').checkErrors | ||
4 | const logger = require('../../../helpers/logger') | ||
5 | |||
6 | const validatorsRemoteVideos = { | ||
7 | remoteVideos | ||
8 | } | ||
9 | |||
10 | function remoteVideos (req, res, next) { | ||
11 | req.checkBody('data').isEachRemoteRequestVideosValid() | ||
12 | |||
13 | logger.debug('Checking remoteVideos parameters', { parameters: req.body }) | ||
14 | |||
15 | checkErrors(req, res, next) | ||
16 | } | ||
17 | |||
18 | // --------------------------------------------------------------------------- | ||
19 | |||
20 | module.exports = validatorsRemoteVideos | ||
diff --git a/server/middlewares/validators/sort.js b/server/middlewares/validators/sort.js index 431d3fffd..b7eec0316 100644 --- a/server/middlewares/validators/sort.js +++ b/server/middlewares/validators/sort.js | |||
@@ -6,29 +6,38 @@ const logger = require('../../helpers/logger') | |||
6 | 6 | ||
7 | const validatorsSort = { | 7 | const validatorsSort = { |
8 | usersSort, | 8 | usersSort, |
9 | videoAbusesSort, | ||
9 | videosSort | 10 | videosSort |
10 | } | 11 | } |
11 | 12 | ||
12 | function usersSort (req, res, next) { | 13 | function usersSort (req, res, next) { |
13 | const sortableColumns = constants.SORTABLE_COLUMNS.USERS | 14 | const sortableColumns = constants.SORTABLE_COLUMNS.USERS |
14 | 15 | ||
15 | req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns) | 16 | checkSort(req, res, next, sortableColumns) |
17 | } | ||
16 | 18 | ||
17 | logger.debug('Checking sort parameters', { parameters: req.query }) | 19 | function videoAbusesSort (req, res, next) { |
20 | const sortableColumns = constants.SORTABLE_COLUMNS.VIDEO_ABUSES | ||
18 | 21 | ||
19 | checkErrors(req, res, next) | 22 | checkSort(req, res, next, sortableColumns) |
20 | } | 23 | } |
21 | 24 | ||
22 | function videosSort (req, res, next) { | 25 | function videosSort (req, res, next) { |
23 | const sortableColumns = constants.SORTABLE_COLUMNS.VIDEOS | 26 | const sortableColumns = constants.SORTABLE_COLUMNS.VIDEOS |
24 | 27 | ||
28 | checkSort(req, res, next, sortableColumns) | ||
29 | } | ||
30 | |||
31 | // --------------------------------------------------------------------------- | ||
32 | |||
33 | module.exports = validatorsSort | ||
34 | |||
35 | // --------------------------------------------------------------------------- | ||
36 | |||
37 | function checkSort (req, res, next, sortableColumns) { | ||
25 | req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns) | 38 | req.checkQuery('sort', 'Should have correct sortable column').optional().isIn(sortableColumns) |
26 | 39 | ||
27 | logger.debug('Checking sort parameters', { parameters: req.query }) | 40 | logger.debug('Checking sort parameters', { parameters: req.query }) |
28 | 41 | ||
29 | checkErrors(req, res, next) | 42 | checkErrors(req, res, next) |
30 | } | 43 | } |
31 | |||
32 | // --------------------------------------------------------------------------- | ||
33 | |||
34 | module.exports = validatorsSort | ||
diff --git a/server/middlewares/validators/users.js b/server/middlewares/validators/users.js index 02e4f34cb..0629550bc 100644 --- a/server/middlewares/validators/users.js +++ b/server/middlewares/validators/users.js | |||
@@ -1,12 +1,9 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const mongoose = require('mongoose') | ||
4 | |||
5 | const checkErrors = require('./utils').checkErrors | 3 | const checkErrors = require('./utils').checkErrors |
4 | const db = require('../../initializers/database') | ||
6 | const logger = require('../../helpers/logger') | 5 | const logger = require('../../helpers/logger') |
7 | 6 | ||
8 | const User = mongoose.model('User') | ||
9 | |||
10 | const validatorsUsers = { | 7 | const validatorsUsers = { |
11 | usersAdd, | 8 | usersAdd, |
12 | usersRemove, | 9 | usersRemove, |
@@ -20,7 +17,7 @@ function usersAdd (req, res, next) { | |||
20 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) | 17 | logger.debug('Checking usersAdd parameters', { parameters: req.body }) |
21 | 18 | ||
22 | checkErrors(req, res, function () { | 19 | checkErrors(req, res, function () { |
23 | User.loadByUsername(req.body.username, function (err, user) { | 20 | db.User.loadByUsername(req.body.username, function (err, user) { |
24 | if (err) { | 21 | if (err) { |
25 | logger.error('Error in usersAdd request validator.', { error: err }) | 22 | logger.error('Error in usersAdd request validator.', { error: err }) |
26 | return res.sendStatus(500) | 23 | return res.sendStatus(500) |
@@ -34,12 +31,12 @@ function usersAdd (req, res, next) { | |||
34 | } | 31 | } |
35 | 32 | ||
36 | function usersRemove (req, res, next) { | 33 | function usersRemove (req, res, next) { |
37 | req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() | 34 | req.checkParams('id', 'Should have a valid id').notEmpty().isInt() |
38 | 35 | ||
39 | logger.debug('Checking usersRemove parameters', { parameters: req.params }) | 36 | logger.debug('Checking usersRemove parameters', { parameters: req.params }) |
40 | 37 | ||
41 | checkErrors(req, res, function () { | 38 | checkErrors(req, res, function () { |
42 | User.loadById(req.params.id, function (err, user) { | 39 | db.User.loadById(req.params.id, function (err, user) { |
43 | if (err) { | 40 | if (err) { |
44 | logger.error('Error in usersRemove request validator.', { error: err }) | 41 | logger.error('Error in usersRemove request validator.', { error: err }) |
45 | return res.sendStatus(500) | 42 | return res.sendStatus(500) |
@@ -55,7 +52,7 @@ function usersRemove (req, res, next) { | |||
55 | } | 52 | } |
56 | 53 | ||
57 | function usersUpdate (req, res, next) { | 54 | function usersUpdate (req, res, next) { |
58 | req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() | 55 | req.checkParams('id', 'Should have a valid id').notEmpty().isInt() |
59 | // Add old password verification | 56 | // Add old password verification |
60 | req.checkBody('password', 'Should have a valid password').isUserPasswordValid() | 57 | req.checkBody('password', 'Should have a valid password').isUserPasswordValid() |
61 | 58 | ||
diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js index 76e943e77..4fe6dcd8b 100644 --- a/server/middlewares/validators/videos.js +++ b/server/middlewares/validators/videos.js | |||
@@ -1,19 +1,19 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const mongoose = require('mongoose') | ||
4 | |||
5 | const checkErrors = require('./utils').checkErrors | 3 | const checkErrors = require('./utils').checkErrors |
6 | const constants = require('../../initializers/constants') | 4 | const constants = require('../../initializers/constants') |
7 | const customVideosValidators = require('../../helpers/custom-validators').videos | 5 | const customVideosValidators = require('../../helpers/custom-validators').videos |
6 | const db = require('../../initializers/database') | ||
8 | const logger = require('../../helpers/logger') | 7 | const logger = require('../../helpers/logger') |
9 | 8 | ||
10 | const Video = mongoose.model('Video') | ||
11 | |||
12 | const validatorsVideos = { | 9 | const validatorsVideos = { |
13 | videosAdd, | 10 | videosAdd, |
11 | videosUpdate, | ||
14 | videosGet, | 12 | videosGet, |
15 | videosRemove, | 13 | videosRemove, |
16 | videosSearch | 14 | videosSearch, |
15 | |||
16 | videoAbuseReport | ||
17 | } | 17 | } |
18 | 18 | ||
19 | function videosAdd (req, res, next) { | 19 | function videosAdd (req, res, next) { |
@@ -29,7 +29,7 @@ function videosAdd (req, res, next) { | |||
29 | checkErrors(req, res, function () { | 29 | checkErrors(req, res, function () { |
30 | const videoFile = req.files.videofile[0] | 30 | const videoFile = req.files.videofile[0] |
31 | 31 | ||
32 | Video.getDurationFromFile(videoFile.path, function (err, duration) { | 32 | db.Video.getDurationFromFile(videoFile.path, function (err, duration) { |
33 | if (err) { | 33 | if (err) { |
34 | return res.status(400).send('Cannot retrieve metadata of the file.') | 34 | return res.status(400).send('Cannot retrieve metadata of the file.') |
35 | } | 35 | } |
@@ -44,40 +44,56 @@ function videosAdd (req, res, next) { | |||
44 | }) | 44 | }) |
45 | } | 45 | } |
46 | 46 | ||
47 | function videosGet (req, res, next) { | 47 | function videosUpdate (req, res, next) { |
48 | req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() | 48 | req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4) |
49 | req.checkBody('name', 'Should have a valid name').optional().isVideoNameValid() | ||
50 | req.checkBody('description', 'Should have a valid description').optional().isVideoDescriptionValid() | ||
51 | req.checkBody('tags', 'Should have correct tags').optional().isVideoTagsValid() | ||
49 | 52 | ||
50 | logger.debug('Checking videosGet parameters', { parameters: req.params }) | 53 | logger.debug('Checking videosUpdate parameters', { parameters: req.body }) |
51 | 54 | ||
52 | checkErrors(req, res, function () { | 55 | checkErrors(req, res, function () { |
53 | Video.load(req.params.id, function (err, video) { | 56 | checkVideoExists(req.params.id, res, function () { |
54 | if (err) { | 57 | // We need to make additional checks |
55 | logger.error('Error in videosGet request validator.', { error: err }) | 58 | if (res.locals.video.isOwned() === false) { |
56 | return res.sendStatus(500) | 59 | return res.status(403).send('Cannot update video of another pod') |
57 | } | 60 | } |
58 | 61 | ||
59 | if (!video) return res.status(404).send('Video not found') | 62 | if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) { |
63 | return res.status(403).send('Cannot update video of another user') | ||
64 | } | ||
60 | 65 | ||
61 | next() | 66 | next() |
62 | }) | 67 | }) |
63 | }) | 68 | }) |
64 | } | 69 | } |
65 | 70 | ||
71 | function videosGet (req, res, next) { | ||
72 | req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4) | ||
73 | |||
74 | logger.debug('Checking videosGet parameters', { parameters: req.params }) | ||
75 | |||
76 | checkErrors(req, res, function () { | ||
77 | checkVideoExists(req.params.id, res, next) | ||
78 | }) | ||
79 | } | ||
80 | |||
66 | function videosRemove (req, res, next) { | 81 | function videosRemove (req, res, next) { |
67 | req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() | 82 | req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4) |
68 | 83 | ||
69 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) | 84 | logger.debug('Checking videosRemove parameters', { parameters: req.params }) |
70 | 85 | ||
71 | checkErrors(req, res, function () { | 86 | checkErrors(req, res, function () { |
72 | Video.load(req.params.id, function (err, video) { | 87 | checkVideoExists(req.params.id, res, function () { |
73 | if (err) { | 88 | // We need to make additional checks |
74 | logger.error('Error in videosRemove request validator.', { error: err }) | 89 | |
75 | return res.sendStatus(500) | 90 | if (res.locals.video.isOwned() === false) { |
91 | return res.status(403).send('Cannot remove video of another pod') | ||
76 | } | 92 | } |
77 | 93 | ||
78 | if (!video) return res.status(404).send('Video not found') | 94 | if (res.locals.video.Author.userId !== res.locals.oauth.token.User.id) { |
79 | else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod') | 95 | return res.status(403).send('Cannot remove video of another user') |
80 | else if (video.author !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user') | 96 | } |
81 | 97 | ||
82 | next() | 98 | next() |
83 | }) | 99 | }) |
@@ -94,6 +110,33 @@ function videosSearch (req, res, next) { | |||
94 | checkErrors(req, res, next) | 110 | checkErrors(req, res, next) |
95 | } | 111 | } |
96 | 112 | ||
113 | function videoAbuseReport (req, res, next) { | ||
114 | req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4) | ||
115 | req.checkBody('reason', 'Should have a valid reason').isVideoAbuseReasonValid() | ||
116 | |||
117 | logger.debug('Checking videoAbuseReport parameters', { parameters: req.body }) | ||
118 | |||
119 | checkErrors(req, res, function () { | ||
120 | checkVideoExists(req.params.id, res, next) | ||
121 | }) | ||
122 | } | ||
123 | |||
97 | // --------------------------------------------------------------------------- | 124 | // --------------------------------------------------------------------------- |
98 | 125 | ||
99 | module.exports = validatorsVideos | 126 | module.exports = validatorsVideos |
127 | |||
128 | // --------------------------------------------------------------------------- | ||
129 | |||
130 | function checkVideoExists (id, res, callback) { | ||
131 | db.Video.loadAndPopulateAuthorAndPodAndTags(id, function (err, video) { | ||
132 | if (err) { | ||
133 | logger.error('Error in video request validator.', { error: err }) | ||
134 | return res.sendStatus(500) | ||
135 | } | ||
136 | |||
137 | if (!video) return res.status(404).send('Video not found') | ||
138 | |||
139 | res.locals.video = video | ||
140 | callback() | ||
141 | }) | ||
142 | } | ||
diff --git a/server/models/application.js b/server/models/application.js index 452ac4283..46dcfde33 100644 --- a/server/models/application.js +++ b/server/models/application.js | |||
@@ -1,31 +1,52 @@ | |||
1 | const mongoose = require('mongoose') | 1 | 'use strict' |
2 | |||
3 | module.exports = function (sequelize, DataTypes) { | ||
4 | const Application = sequelize.define('Application', | ||
5 | { | ||
6 | migrationVersion: { | ||
7 | type: DataTypes.INTEGER, | ||
8 | defaultValue: 0, | ||
9 | allowNull: false, | ||
10 | validate: { | ||
11 | isInt: true | ||
12 | } | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | classMethods: { | ||
17 | loadMigrationVersion, | ||
18 | updateMigrationVersion | ||
19 | } | ||
20 | } | ||
21 | ) | ||
22 | |||
23 | return Application | ||
24 | } | ||
2 | 25 | ||
3 | // --------------------------------------------------------------------------- | 26 | // --------------------------------------------------------------------------- |
4 | 27 | ||
5 | const ApplicationSchema = mongoose.Schema({ | 28 | function loadMigrationVersion (callback) { |
6 | mongoSchemaVersion: { | 29 | const query = { |
7 | type: Number, | 30 | attributes: [ 'migrationVersion' ] |
8 | default: 0 | ||
9 | } | 31 | } |
10 | }) | ||
11 | |||
12 | ApplicationSchema.statics = { | ||
13 | loadMongoSchemaVersion, | ||
14 | updateMongoSchemaVersion | ||
15 | } | ||
16 | |||
17 | mongoose.model('Application', ApplicationSchema) | ||
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | 32 | ||
21 | function loadMongoSchemaVersion (callback) { | 33 | return this.findOne(query).asCallback(function (err, data) { |
22 | return this.findOne({}, { mongoSchemaVersion: 1 }, function (err, data) { | 34 | const version = data ? data.migrationVersion : 0 |
23 | const version = data ? data.mongoSchemaVersion : 0 | ||
24 | 35 | ||
25 | return callback(err, version) | 36 | return callback(err, version) |
26 | }) | 37 | }) |
27 | } | 38 | } |
28 | 39 | ||
29 | function updateMongoSchemaVersion (newVersion, callback) { | 40 | function updateMigrationVersion (newVersion, transaction, callback) { |
30 | return this.update({}, { mongoSchemaVersion: newVersion }, callback) | 41 | const options = { |
42 | where: {} | ||
43 | } | ||
44 | |||
45 | if (!callback) { | ||
46 | transaction = callback | ||
47 | } else { | ||
48 | options.transaction = transaction | ||
49 | } | ||
50 | |||
51 | return this.update({ migrationVersion: newVersion }, options).asCallback(callback) | ||
31 | } | 52 | } |
diff --git a/server/models/author.js b/server/models/author.js new file mode 100644 index 000000000..7d15fb6ec --- /dev/null +++ b/server/models/author.js | |||
@@ -0,0 +1,85 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const customUsersValidators = require('../helpers/custom-validators').users | ||
4 | |||
5 | module.exports = function (sequelize, DataTypes) { | ||
6 | const Author = sequelize.define('Author', | ||
7 | { | ||
8 | name: { | ||
9 | type: DataTypes.STRING, | ||
10 | allowNull: false, | ||
11 | validate: { | ||
12 | usernameValid: function (value) { | ||
13 | const res = customUsersValidators.isUserUsernameValid(value) | ||
14 | if (res === false) throw new Error('Username is not valid.') | ||
15 | } | ||
16 | } | ||
17 | } | ||
18 | }, | ||
19 | { | ||
20 | indexes: [ | ||
21 | { | ||
22 | fields: [ 'name' ] | ||
23 | }, | ||
24 | { | ||
25 | fields: [ 'podId' ] | ||
26 | }, | ||
27 | { | ||
28 | fields: [ 'userId' ] | ||
29 | } | ||
30 | ], | ||
31 | classMethods: { | ||
32 | associate, | ||
33 | |||
34 | findOrCreateAuthor | ||
35 | } | ||
36 | } | ||
37 | ) | ||
38 | |||
39 | return Author | ||
40 | } | ||
41 | |||
42 | // --------------------------------------------------------------------------- | ||
43 | |||
44 | function associate (models) { | ||
45 | this.belongsTo(models.Pod, { | ||
46 | foreignKey: { | ||
47 | name: 'podId', | ||
48 | allowNull: true | ||
49 | }, | ||
50 | onDelete: 'cascade' | ||
51 | }) | ||
52 | |||
53 | this.belongsTo(models.User, { | ||
54 | foreignKey: { | ||
55 | name: 'userId', | ||
56 | allowNull: true | ||
57 | }, | ||
58 | onDelete: 'cascade' | ||
59 | }) | ||
60 | } | ||
61 | |||
62 | function findOrCreateAuthor (name, podId, userId, transaction, callback) { | ||
63 | if (!callback) { | ||
64 | callback = transaction | ||
65 | transaction = null | ||
66 | } | ||
67 | |||
68 | const author = { | ||
69 | name, | ||
70 | podId, | ||
71 | userId | ||
72 | } | ||
73 | |||
74 | const query = { | ||
75 | where: author, | ||
76 | defaults: author | ||
77 | } | ||
78 | |||
79 | if (transaction) query.transaction = transaction | ||
80 | |||
81 | this.findOrCreate(query).asCallback(function (err, result) { | ||
82 | // [ instance, wasCreated ] | ||
83 | return callback(err, result[0]) | ||
84 | }) | ||
85 | } | ||
diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js index a1aefa985..021a34007 100644 --- a/server/models/oauth-client.js +++ b/server/models/oauth-client.js | |||
@@ -1,33 +1,62 @@ | |||
1 | const mongoose = require('mongoose') | 1 | 'use strict' |
2 | 2 | ||
3 | // --------------------------------------------------------------------------- | 3 | module.exports = function (sequelize, DataTypes) { |
4 | 4 | const OAuthClient = sequelize.define('OAuthClient', | |
5 | const OAuthClientSchema = mongoose.Schema({ | 5 | { |
6 | clientSecret: String, | 6 | clientId: { |
7 | grants: Array, | 7 | type: DataTypes.STRING, |
8 | redirectUris: Array | 8 | allowNull: false |
9 | }) | 9 | }, |
10 | 10 | clientSecret: { | |
11 | OAuthClientSchema.path('clientSecret').required(true) | 11 | type: DataTypes.STRING, |
12 | 12 | allowNull: false | |
13 | OAuthClientSchema.statics = { | 13 | }, |
14 | getByIdAndSecret, | 14 | grants: { |
15 | list, | 15 | type: DataTypes.ARRAY(DataTypes.STRING) |
16 | loadFirstClient | 16 | }, |
17 | redirectUris: { | ||
18 | type: DataTypes.ARRAY(DataTypes.STRING) | ||
19 | } | ||
20 | }, | ||
21 | { | ||
22 | indexes: [ | ||
23 | { | ||
24 | fields: [ 'clientId' ], | ||
25 | unique: true | ||
26 | }, | ||
27 | { | ||
28 | fields: [ 'clientId', 'clientSecret' ], | ||
29 | unique: true | ||
30 | } | ||
31 | ], | ||
32 | classMethods: { | ||
33 | countTotal, | ||
34 | getByIdAndSecret, | ||
35 | loadFirstClient | ||
36 | } | ||
37 | } | ||
38 | ) | ||
39 | |||
40 | return OAuthClient | ||
17 | } | 41 | } |
18 | 42 | ||
19 | mongoose.model('OAuthClient', OAuthClientSchema) | ||
20 | |||
21 | // --------------------------------------------------------------------------- | 43 | // --------------------------------------------------------------------------- |
22 | 44 | ||
23 | function list (callback) { | 45 | function countTotal (callback) { |
24 | return this.find(callback) | 46 | return this.count().asCallback(callback) |
25 | } | 47 | } |
26 | 48 | ||
27 | function loadFirstClient (callback) { | 49 | function loadFirstClient (callback) { |
28 | return this.findOne({}, callback) | 50 | return this.findOne().asCallback(callback) |
29 | } | 51 | } |
30 | 52 | ||
31 | function getByIdAndSecret (id, clientSecret) { | 53 | function getByIdAndSecret (clientId, clientSecret) { |
32 | return this.findOne({ _id: id, clientSecret: clientSecret }).exec() | 54 | const query = { |
55 | where: { | ||
56 | clientId: clientId, | ||
57 | clientSecret: clientSecret | ||
58 | } | ||
59 | } | ||
60 | |||
61 | return this.findOne(query) | ||
33 | } | 62 | } |
diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js index aff73bfb1..68e7c9ff7 100644 --- a/server/models/oauth-token.js +++ b/server/models/oauth-token.js | |||
@@ -1,42 +1,96 @@ | |||
1 | const mongoose = require('mongoose') | 1 | 'use strict' |
2 | 2 | ||
3 | const logger = require('../helpers/logger') | 3 | const logger = require('../helpers/logger') |
4 | 4 | ||
5 | // --------------------------------------------------------------------------- | 5 | // --------------------------------------------------------------------------- |
6 | 6 | ||
7 | const OAuthTokenSchema = mongoose.Schema({ | 7 | module.exports = function (sequelize, DataTypes) { |
8 | accessToken: String, | 8 | const OAuthToken = sequelize.define('OAuthToken', |
9 | accessTokenExpiresAt: Date, | 9 | { |
10 | client: { type: mongoose.Schema.Types.ObjectId, ref: 'OAuthClient' }, | 10 | accessToken: { |
11 | refreshToken: String, | 11 | type: DataTypes.STRING, |
12 | refreshTokenExpiresAt: Date, | 12 | allowNull: false |
13 | user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } | 13 | }, |
14 | }) | 14 | accessTokenExpiresAt: { |
15 | 15 | type: DataTypes.DATE, | |
16 | OAuthTokenSchema.path('accessToken').required(true) | 16 | allowNull: false |
17 | OAuthTokenSchema.path('client').required(true) | 17 | }, |
18 | OAuthTokenSchema.path('user').required(true) | 18 | refreshToken: { |
19 | 19 | type: DataTypes.STRING, | |
20 | OAuthTokenSchema.statics = { | 20 | allowNull: false |
21 | getByRefreshTokenAndPopulateClient, | 21 | }, |
22 | getByTokenAndPopulateUser, | 22 | refreshTokenExpiresAt: { |
23 | getByRefreshTokenAndPopulateUser, | 23 | type: DataTypes.DATE, |
24 | removeByUserId | 24 | allowNull: false |
25 | } | 25 | } |
26 | }, | ||
27 | { | ||
28 | indexes: [ | ||
29 | { | ||
30 | fields: [ 'refreshToken' ], | ||
31 | unique: true | ||
32 | }, | ||
33 | { | ||
34 | fields: [ 'accessToken' ], | ||
35 | unique: true | ||
36 | }, | ||
37 | { | ||
38 | fields: [ 'userId' ] | ||
39 | }, | ||
40 | { | ||
41 | fields: [ 'oAuthClientId' ] | ||
42 | } | ||
43 | ], | ||
44 | classMethods: { | ||
45 | associate, | ||
46 | |||
47 | getByRefreshTokenAndPopulateClient, | ||
48 | getByTokenAndPopulateUser, | ||
49 | getByRefreshTokenAndPopulateUser, | ||
50 | removeByUserId | ||
51 | } | ||
52 | } | ||
53 | ) | ||
26 | 54 | ||
27 | mongoose.model('OAuthToken', OAuthTokenSchema) | 55 | return OAuthToken |
56 | } | ||
28 | 57 | ||
29 | // --------------------------------------------------------------------------- | 58 | // --------------------------------------------------------------------------- |
30 | 59 | ||
60 | function associate (models) { | ||
61 | this.belongsTo(models.User, { | ||
62 | foreignKey: { | ||
63 | name: 'userId', | ||
64 | allowNull: false | ||
65 | }, | ||
66 | onDelete: 'cascade' | ||
67 | }) | ||
68 | |||
69 | this.belongsTo(models.OAuthClient, { | ||
70 | foreignKey: { | ||
71 | name: 'oAuthClientId', | ||
72 | allowNull: false | ||
73 | }, | ||
74 | onDelete: 'cascade' | ||
75 | }) | ||
76 | } | ||
77 | |||
31 | function getByRefreshTokenAndPopulateClient (refreshToken) { | 78 | function getByRefreshTokenAndPopulateClient (refreshToken) { |
32 | return this.findOne({ refreshToken: refreshToken }).populate('client').exec().then(function (token) { | 79 | const query = { |
80 | where: { | ||
81 | refreshToken: refreshToken | ||
82 | }, | ||
83 | include: [ this.associations.OAuthClient ] | ||
84 | } | ||
85 | |||
86 | return this.findOne(query).then(function (token) { | ||
33 | if (!token) return token | 87 | if (!token) return token |
34 | 88 | ||
35 | const tokenInfos = { | 89 | const tokenInfos = { |
36 | refreshToken: token.refreshToken, | 90 | refreshToken: token.refreshToken, |
37 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, | 91 | refreshTokenExpiresAt: token.refreshTokenExpiresAt, |
38 | client: { | 92 | client: { |
39 | id: token.client._id.toString() | 93 | id: token.client.id |
40 | }, | 94 | }, |
41 | user: { | 95 | user: { |
42 | id: token.user | 96 | id: token.user |
@@ -50,13 +104,41 @@ function getByRefreshTokenAndPopulateClient (refreshToken) { | |||
50 | } | 104 | } |
51 | 105 | ||
52 | function getByTokenAndPopulateUser (bearerToken) { | 106 | function getByTokenAndPopulateUser (bearerToken) { |
53 | return this.findOne({ accessToken: bearerToken }).populate('user').exec() | 107 | const query = { |
108 | where: { | ||
109 | accessToken: bearerToken | ||
110 | }, | ||
111 | include: [ this.sequelize.models.User ] | ||
112 | } | ||
113 | |||
114 | return this.findOne(query).then(function (token) { | ||
115 | if (token) token.user = token.User | ||
116 | |||
117 | return token | ||
118 | }) | ||
54 | } | 119 | } |
55 | 120 | ||
56 | function getByRefreshTokenAndPopulateUser (refreshToken) { | 121 | function getByRefreshTokenAndPopulateUser (refreshToken) { |
57 | return this.findOne({ refreshToken: refreshToken }).populate('user').exec() | 122 | const query = { |
123 | where: { | ||
124 | refreshToken: refreshToken | ||
125 | }, | ||
126 | include: [ this.sequelize.models.User ] | ||
127 | } | ||
128 | |||
129 | return this.findOne(query).then(function (token) { | ||
130 | token.user = token.User | ||
131 | |||
132 | return token | ||
133 | }) | ||
58 | } | 134 | } |
59 | 135 | ||
60 | function removeByUserId (userId, callback) { | 136 | function removeByUserId (userId, callback) { |
61 | return this.remove({ user: userId }, callback) | 137 | const query = { |
138 | where: { | ||
139 | userId: userId | ||
140 | } | ||
141 | } | ||
142 | |||
143 | return this.destroy(query).asCallback(callback) | ||
62 | } | 144 | } |
diff --git a/server/models/pod.js b/server/models/pod.js new file mode 100644 index 000000000..b3c6db8e8 --- /dev/null +++ b/server/models/pod.js | |||
@@ -0,0 +1,200 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const map = require('lodash/map') | ||
4 | |||
5 | const constants = require('../initializers/constants') | ||
6 | const customPodsValidators = require('../helpers/custom-validators').pods | ||
7 | |||
8 | // --------------------------------------------------------------------------- | ||
9 | |||
10 | module.exports = function (sequelize, DataTypes) { | ||
11 | const Pod = sequelize.define('Pod', | ||
12 | { | ||
13 | host: { | ||
14 | type: DataTypes.STRING, | ||
15 | allowNull: false, | ||
16 | validate: { | ||
17 | isHost: function (value) { | ||
18 | const res = customPodsValidators.isHostValid(value) | ||
19 | if (res === false) throw new Error('Host not valid.') | ||
20 | } | ||
21 | } | ||
22 | }, | ||
23 | publicKey: { | ||
24 | type: DataTypes.STRING(5000), | ||
25 | allowNull: false | ||
26 | }, | ||
27 | score: { | ||
28 | type: DataTypes.INTEGER, | ||
29 | defaultValue: constants.FRIEND_SCORE.BASE, | ||
30 | allowNull: false, | ||
31 | validate: { | ||
32 | isInt: true, | ||
33 | max: constants.FRIEND_SCORE.MAX | ||
34 | } | ||
35 | } | ||
36 | }, | ||
37 | { | ||
38 | indexes: [ | ||
39 | { | ||
40 | fields: [ 'host' ] | ||
41 | }, | ||
42 | { | ||
43 | fields: [ 'score' ] | ||
44 | } | ||
45 | ], | ||
46 | classMethods: { | ||
47 | associate, | ||
48 | |||
49 | countAll, | ||
50 | incrementScores, | ||
51 | list, | ||
52 | listAllIds, | ||
53 | listRandomPodIdsWithRequest, | ||
54 | listBadPods, | ||
55 | load, | ||
56 | loadByHost, | ||
57 | removeAll | ||
58 | }, | ||
59 | instanceMethods: { | ||
60 | toFormatedJSON | ||
61 | } | ||
62 | } | ||
63 | ) | ||
64 | |||
65 | return Pod | ||
66 | } | ||
67 | |||
68 | // ------------------------------ METHODS ------------------------------ | ||
69 | |||
70 | function toFormatedJSON () { | ||
71 | const json = { | ||
72 | id: this.id, | ||
73 | host: this.host, | ||
74 | score: this.score, | ||
75 | createdAt: this.createdAt | ||
76 | } | ||
77 | |||
78 | return json | ||
79 | } | ||
80 | |||
81 | // ------------------------------ Statics ------------------------------ | ||
82 | |||
83 | function associate (models) { | ||
84 | this.belongsToMany(models.Request, { | ||
85 | foreignKey: 'podId', | ||
86 | through: models.RequestToPod, | ||
87 | onDelete: 'cascade' | ||
88 | }) | ||
89 | } | ||
90 | |||
91 | function countAll (callback) { | ||
92 | return this.count().asCallback(callback) | ||
93 | } | ||
94 | |||
95 | function incrementScores (ids, value, callback) { | ||
96 | if (!callback) callback = function () {} | ||
97 | |||
98 | const update = { | ||
99 | score: this.sequelize.literal('score +' + value) | ||
100 | } | ||
101 | |||
102 | const options = { | ||
103 | where: { | ||
104 | id: { | ||
105 | $in: ids | ||
106 | } | ||
107 | }, | ||
108 | // In this case score is a literal and not an integer so we do not validate it | ||
109 | validate: false | ||
110 | } | ||
111 | |||
112 | return this.update(update, options).asCallback(callback) | ||
113 | } | ||
114 | |||
115 | function list (callback) { | ||
116 | return this.findAll().asCallback(callback) | ||
117 | } | ||
118 | |||
119 | function listAllIds (transaction, callback) { | ||
120 | if (!callback) { | ||
121 | callback = transaction | ||
122 | transaction = null | ||
123 | } | ||
124 | |||
125 | const query = { | ||
126 | attributes: [ 'id' ] | ||
127 | } | ||
128 | |||
129 | if (transaction) query.transaction = transaction | ||
130 | |||
131 | return this.findAll(query).asCallback(function (err, pods) { | ||
132 | if (err) return callback(err) | ||
133 | |||
134 | return callback(null, map(pods, 'id')) | ||
135 | }) | ||
136 | } | ||
137 | |||
138 | function listRandomPodIdsWithRequest (limit, callback) { | ||
139 | const self = this | ||
140 | |||
141 | self.count().asCallback(function (err, count) { | ||
142 | if (err) return callback(err) | ||
143 | |||
144 | // Optimization... | ||
145 | if (count === 0) return callback(null, []) | ||
146 | |||
147 | let start = Math.floor(Math.random() * count) - limit | ||
148 | if (start < 0) start = 0 | ||
149 | |||
150 | const query = { | ||
151 | attributes: [ 'id' ], | ||
152 | order: [ | ||
153 | [ 'id', 'ASC' ] | ||
154 | ], | ||
155 | offset: start, | ||
156 | limit: limit, | ||
157 | where: { | ||
158 | id: { | ||
159 | $in: [ | ||
160 | this.sequelize.literal('SELECT "podId" FROM "RequestToPods"') | ||
161 | ] | ||
162 | } | ||
163 | } | ||
164 | } | ||
165 | |||
166 | return this.findAll(query).asCallback(function (err, pods) { | ||
167 | if (err) return callback(err) | ||
168 | |||
169 | return callback(null, map(pods, 'id')) | ||
170 | }) | ||
171 | }) | ||
172 | } | ||
173 | |||
174 | function listBadPods (callback) { | ||
175 | const query = { | ||
176 | where: { | ||
177 | score: { $lte: 0 } | ||
178 | } | ||
179 | } | ||
180 | |||
181 | return this.findAll(query).asCallback(callback) | ||
182 | } | ||
183 | |||
184 | function load (id, callback) { | ||
185 | return this.findById(id).asCallback(callback) | ||
186 | } | ||
187 | |||
188 | function loadByHost (host, callback) { | ||
189 | const query = { | ||
190 | where: { | ||
191 | host: host | ||
192 | } | ||
193 | } | ||
194 | |||
195 | return this.findOne(query).asCallback(callback) | ||
196 | } | ||
197 | |||
198 | function removeAll (callback) { | ||
199 | return this.destroy().asCallback(callback) | ||
200 | } | ||
diff --git a/server/models/pods.js b/server/models/pods.js deleted file mode 100644 index 49c73472a..000000000 --- a/server/models/pods.js +++ /dev/null | |||
@@ -1,119 +0,0 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const each = require('async/each') | ||
4 | const mongoose = require('mongoose') | ||
5 | const map = require('lodash/map') | ||
6 | const validator = require('express-validator').validator | ||
7 | |||
8 | const constants = require('../initializers/constants') | ||
9 | |||
10 | const Video = mongoose.model('Video') | ||
11 | |||
12 | // --------------------------------------------------------------------------- | ||
13 | |||
14 | const PodSchema = mongoose.Schema({ | ||
15 | host: String, | ||
16 | publicKey: String, | ||
17 | score: { type: Number, max: constants.FRIEND_SCORE.MAX }, | ||
18 | createdDate: { | ||
19 | type: Date, | ||
20 | default: Date.now | ||
21 | } | ||
22 | }) | ||
23 | |||
24 | PodSchema.path('host').validate(validator.isURL) | ||
25 | PodSchema.path('publicKey').required(true) | ||
26 | PodSchema.path('score').validate(function (value) { return !isNaN(value) }) | ||
27 | |||
28 | PodSchema.methods = { | ||
29 | toFormatedJSON | ||
30 | } | ||
31 | |||
32 | PodSchema.statics = { | ||
33 | countAll, | ||
34 | incrementScores, | ||
35 | list, | ||
36 | listAllIds, | ||
37 | listBadPods, | ||
38 | load, | ||
39 | loadByHost, | ||
40 | removeAll | ||
41 | } | ||
42 | |||
43 | PodSchema.pre('save', function (next) { | ||
44 | const self = this | ||
45 | |||
46 | Pod.loadByHost(this.host, function (err, pod) { | ||
47 | if (err) return next(err) | ||
48 | |||
49 | if (pod) return next(new Error('Pod already exists.')) | ||
50 | |||
51 | self.score = constants.FRIEND_SCORE.BASE | ||
52 | return next() | ||
53 | }) | ||
54 | }) | ||
55 | |||
56 | PodSchema.pre('remove', function (next) { | ||
57 | // Remove the videos owned by this pod too | ||
58 | Video.listByHost(this.host, function (err, videos) { | ||
59 | if (err) return next(err) | ||
60 | |||
61 | each(videos, function (video, callbackEach) { | ||
62 | video.remove(callbackEach) | ||
63 | }, next) | ||
64 | }) | ||
65 | }) | ||
66 | |||
67 | const Pod = mongoose.model('Pod', PodSchema) | ||
68 | |||
69 | // ------------------------------ METHODS ------------------------------ | ||
70 | |||
71 | function toFormatedJSON () { | ||
72 | const json = { | ||
73 | id: this._id, | ||
74 | host: this.host, | ||
75 | score: this.score, | ||
76 | createdDate: this.createdDate | ||
77 | } | ||
78 | |||
79 | return json | ||
80 | } | ||
81 | |||
82 | // ------------------------------ Statics ------------------------------ | ||
83 | |||
84 | function countAll (callback) { | ||
85 | return this.count(callback) | ||
86 | } | ||
87 | |||
88 | function incrementScores (ids, value, callback) { | ||
89 | if (!callback) callback = function () {} | ||
90 | return this.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback) | ||
91 | } | ||
92 | |||
93 | function list (callback) { | ||
94 | return this.find(callback) | ||
95 | } | ||
96 | |||
97 | function listAllIds (callback) { | ||
98 | return this.find({}, { _id: 1 }, function (err, pods) { | ||
99 | if (err) return callback(err) | ||
100 | |||
101 | return callback(null, map(pods, '_id')) | ||
102 | }) | ||
103 | } | ||
104 | |||
105 | function listBadPods (callback) { | ||
106 | return this.find({ score: 0 }, callback) | ||
107 | } | ||
108 | |||
109 | function load (id, callback) { | ||
110 | return this.findById(id, callback) | ||
111 | } | ||
112 | |||
113 | function loadByHost (host, callback) { | ||
114 | return this.findOne({ host }, callback) | ||
115 | } | ||
116 | |||
117 | function removeAll (callback) { | ||
118 | return this.remove({}, callback) | ||
119 | } | ||
diff --git a/server/models/request-to-pod.js b/server/models/request-to-pod.js new file mode 100644 index 000000000..f42a53458 --- /dev/null +++ b/server/models/request-to-pod.js | |||
@@ -0,0 +1,42 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | // --------------------------------------------------------------------------- | ||
4 | |||
5 | module.exports = function (sequelize, DataTypes) { | ||
6 | const RequestToPod = sequelize.define('RequestToPod', {}, { | ||
7 | indexes: [ | ||
8 | { | ||
9 | fields: [ 'requestId' ] | ||
10 | }, | ||
11 | { | ||
12 | fields: [ 'podId' ] | ||
13 | }, | ||
14 | { | ||
15 | fields: [ 'requestId', 'podId' ], | ||
16 | unique: true | ||
17 | } | ||
18 | ], | ||
19 | classMethods: { | ||
20 | removePodOf | ||
21 | } | ||
22 | }) | ||
23 | |||
24 | return RequestToPod | ||
25 | } | ||
26 | |||
27 | // --------------------------------------------------------------------------- | ||
28 | |||
29 | function removePodOf (requestsIds, podId, callback) { | ||
30 | if (!callback) callback = function () {} | ||
31 | |||
32 | const query = { | ||
33 | where: { | ||
34 | requestId: { | ||
35 | $in: requestsIds | ||
36 | }, | ||
37 | podId: podId | ||
38 | } | ||
39 | } | ||
40 | |||
41 | this.destroy(query).asCallback(callback) | ||
42 | } | ||
diff --git a/server/models/request.js b/server/models/request.js index c2cfe83ce..cd52ea767 100644 --- a/server/models/request.js +++ b/server/models/request.js | |||
@@ -2,66 +2,60 @@ | |||
2 | 2 | ||
3 | const each = require('async/each') | 3 | const each = require('async/each') |
4 | const eachLimit = require('async/eachLimit') | 4 | const eachLimit = require('async/eachLimit') |
5 | const values = require('lodash/values') | ||
6 | const mongoose = require('mongoose') | ||
7 | const waterfall = require('async/waterfall') | 5 | const waterfall = require('async/waterfall') |
6 | const values = require('lodash/values') | ||
8 | 7 | ||
9 | const constants = require('../initializers/constants') | 8 | const constants = require('../initializers/constants') |
10 | const logger = require('../helpers/logger') | 9 | const logger = require('../helpers/logger') |
11 | const requests = require('../helpers/requests') | 10 | const requests = require('../helpers/requests') |
12 | 11 | ||
13 | const Pod = mongoose.model('Pod') | ||
14 | |||
15 | let timer = null | 12 | let timer = null |
16 | let lastRequestTimestamp = 0 | 13 | let lastRequestTimestamp = 0 |
17 | 14 | ||
18 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
19 | 16 | ||
20 | const RequestSchema = mongoose.Schema({ | 17 | module.exports = function (sequelize, DataTypes) { |
21 | request: mongoose.Schema.Types.Mixed, | 18 | const Request = sequelize.define('Request', |
22 | endpoint: { | 19 | { |
23 | type: String, | 20 | request: { |
24 | enum: [ values(constants.REQUEST_ENDPOINTS) ] | 21 | type: DataTypes.JSON, |
25 | }, | 22 | allowNull: false |
26 | to: [ | 23 | }, |
24 | endpoint: { | ||
25 | type: DataTypes.ENUM(values(constants.REQUEST_ENDPOINTS)), | ||
26 | allowNull: false | ||
27 | } | ||
28 | }, | ||
27 | { | 29 | { |
28 | type: mongoose.Schema.Types.ObjectId, | 30 | classMethods: { |
29 | ref: 'Pod' | 31 | associate, |
32 | |||
33 | activate, | ||
34 | countTotalRequests, | ||
35 | deactivate, | ||
36 | flush, | ||
37 | forceSend, | ||
38 | remainingMilliSeconds | ||
39 | } | ||
30 | } | 40 | } |
31 | ] | 41 | ) |
32 | }) | ||
33 | |||
34 | RequestSchema.statics = { | ||
35 | activate, | ||
36 | deactivate, | ||
37 | flush, | ||
38 | forceSend, | ||
39 | list, | ||
40 | remainingMilliSeconds | ||
41 | } | ||
42 | |||
43 | RequestSchema.pre('save', function (next) { | ||
44 | const self = this | ||
45 | |||
46 | if (self.to.length === 0) { | ||
47 | Pod.listAllIds(function (err, podIds) { | ||
48 | if (err) return next(err) | ||
49 | 42 | ||
50 | // No friends | 43 | return Request |
51 | if (podIds.length === 0) return | 44 | } |
52 | |||
53 | self.to = podIds | ||
54 | return next() | ||
55 | }) | ||
56 | } else { | ||
57 | return next() | ||
58 | } | ||
59 | }) | ||
60 | |||
61 | mongoose.model('Request', RequestSchema) | ||
62 | 45 | ||
63 | // ------------------------------ STATICS ------------------------------ | 46 | // ------------------------------ STATICS ------------------------------ |
64 | 47 | ||
48 | function associate (models) { | ||
49 | this.belongsToMany(models.Pod, { | ||
50 | foreignKey: { | ||
51 | name: 'requestId', | ||
52 | allowNull: false | ||
53 | }, | ||
54 | through: models.RequestToPod, | ||
55 | onDelete: 'CASCADE' | ||
56 | }) | ||
57 | } | ||
58 | |||
65 | function activate () { | 59 | function activate () { |
66 | logger.info('Requests scheduler activated.') | 60 | logger.info('Requests scheduler activated.') |
67 | lastRequestTimestamp = Date.now() | 61 | lastRequestTimestamp = Date.now() |
@@ -73,15 +67,25 @@ function activate () { | |||
73 | }, constants.REQUESTS_INTERVAL) | 67 | }, constants.REQUESTS_INTERVAL) |
74 | } | 68 | } |
75 | 69 | ||
70 | function countTotalRequests (callback) { | ||
71 | const query = { | ||
72 | include: [ this.sequelize.models.Pod ] | ||
73 | } | ||
74 | |||
75 | return this.count(query).asCallback(callback) | ||
76 | } | ||
77 | |||
76 | function deactivate () { | 78 | function deactivate () { |
77 | logger.info('Requests scheduler deactivated.') | 79 | logger.info('Requests scheduler deactivated.') |
78 | clearInterval(timer) | 80 | clearInterval(timer) |
79 | timer = null | 81 | timer = null |
80 | } | 82 | } |
81 | 83 | ||
82 | function flush () { | 84 | function flush (callback) { |
83 | removeAll.call(this, function (err) { | 85 | removeAll.call(this, function (err) { |
84 | if (err) logger.error('Cannot flush the requests.', { error: err }) | 86 | if (err) logger.error('Cannot flush the requests.', { error: err }) |
87 | |||
88 | return callback(err) | ||
85 | }) | 89 | }) |
86 | } | 90 | } |
87 | 91 | ||
@@ -90,10 +94,6 @@ function forceSend () { | |||
90 | makeRequests.call(this) | 94 | makeRequests.call(this) |
91 | } | 95 | } |
92 | 96 | ||
93 | function list (callback) { | ||
94 | this.find({ }, callback) | ||
95 | } | ||
96 | |||
97 | function remainingMilliSeconds () { | 97 | function remainingMilliSeconds () { |
98 | if (timer === null) return -1 | 98 | if (timer === null) return -1 |
99 | 99 | ||
@@ -122,7 +122,7 @@ function makeRequest (toPod, requestEndpoint, requestsToMake, callback) { | |||
122 | 'Error sending secure request to %s pod.', | 122 | 'Error sending secure request to %s pod.', |
123 | toPod.host, | 123 | toPod.host, |
124 | { | 124 | { |
125 | error: err || new Error('Status code not 20x : ' + res.statusCode) | 125 | error: err ? err.message : 'Status code not 20x : ' + res.statusCode |
126 | } | 126 | } |
127 | ) | 127 | ) |
128 | 128 | ||
@@ -136,10 +136,11 @@ function makeRequest (toPod, requestEndpoint, requestsToMake, callback) { | |||
136 | // Make all the requests of the scheduler | 136 | // Make all the requests of the scheduler |
137 | function makeRequests () { | 137 | function makeRequests () { |
138 | const self = this | 138 | const self = this |
139 | const RequestToPod = this.sequelize.models.RequestToPod | ||
139 | 140 | ||
140 | // We limit the size of the requests (REQUESTS_LIMIT) | 141 | // We limit the size of the requests |
141 | // We don't want to stuck with the same failing requests so we get a random list | 142 | // We don't want to stuck with the same failing requests so we get a random list |
142 | listWithLimitAndRandom.call(self, constants.REQUESTS_LIMIT, function (err, requests) { | 143 | listWithLimitAndRandom.call(self, constants.REQUESTS_LIMIT_PODS, constants.REQUESTS_LIMIT_PER_POD, function (err, requests) { |
143 | if (err) { | 144 | if (err) { |
144 | logger.error('Cannot get the list of requests.', { err: err }) | 145 | logger.error('Cannot get the list of requests.', { err: err }) |
145 | return // Abort | 146 | return // Abort |
@@ -151,78 +152,77 @@ function makeRequests () { | |||
151 | return | 152 | return |
152 | } | 153 | } |
153 | 154 | ||
154 | logger.info('Making requests to friends.') | ||
155 | |||
156 | // We want to group requests by destinations pod and endpoint | 155 | // We want to group requests by destinations pod and endpoint |
157 | const requestsToMakeGrouped = {} | 156 | const requestsToMakeGrouped = {} |
157 | Object.keys(requests).forEach(function (toPodId) { | ||
158 | requests[toPodId].forEach(function (data) { | ||
159 | const request = data.request | ||
160 | const pod = data.pod | ||
161 | const hashKey = toPodId + request.endpoint | ||
158 | 162 | ||
159 | requests.forEach(function (poolRequest) { | ||
160 | poolRequest.to.forEach(function (toPodId) { | ||
161 | const hashKey = toPodId + poolRequest.endpoint | ||
162 | if (!requestsToMakeGrouped[hashKey]) { | 163 | if (!requestsToMakeGrouped[hashKey]) { |
163 | requestsToMakeGrouped[hashKey] = { | 164 | requestsToMakeGrouped[hashKey] = { |
164 | toPodId, | 165 | toPod: pod, |
165 | endpoint: poolRequest.endpoint, | 166 | endpoint: request.endpoint, |
166 | ids: [], // pool request ids, to delete them from the DB in the future | 167 | ids: [], // request ids, to delete them from the DB in the future |
167 | datas: [] // requests data, | 168 | datas: [] // requests data, |
168 | } | 169 | } |
169 | } | 170 | } |
170 | 171 | ||
171 | requestsToMakeGrouped[hashKey].ids.push(poolRequest._id) | 172 | requestsToMakeGrouped[hashKey].ids.push(request.id) |
172 | requestsToMakeGrouped[hashKey].datas.push(poolRequest.request) | 173 | requestsToMakeGrouped[hashKey].datas.push(request.request) |
173 | }) | 174 | }) |
174 | }) | 175 | }) |
175 | 176 | ||
177 | logger.info('Making requests to friends.') | ||
178 | |||
176 | const goodPods = [] | 179 | const goodPods = [] |
177 | const badPods = [] | 180 | const badPods = [] |
178 | 181 | ||
179 | eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) { | 182 | eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) { |
180 | const requestToMake = requestsToMakeGrouped[hashKey] | 183 | const requestToMake = requestsToMakeGrouped[hashKey] |
184 | const toPod = requestToMake.toPod | ||
181 | 185 | ||
182 | // FIXME: mongodb request inside a loop :/ | 186 | // Maybe the pod is not our friend anymore so simply remove it |
183 | Pod.load(requestToMake.toPodId, function (err, toPod) { | 187 | if (!toPod) { |
184 | if (err) { | 188 | const requestIdsToDelete = requestToMake.ids |
185 | logger.error('Error finding pod by id.', { err: err }) | ||
186 | return callbackEach() | ||
187 | } | ||
188 | |||
189 | // Maybe the pod is not our friend anymore so simply remove it | ||
190 | if (!toPod) { | ||
191 | const requestIdsToDelete = requestToMake.ids | ||
192 | 189 | ||
193 | logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPodId) | 190 | logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPod.id) |
194 | removePodOf.call(self, requestIdsToDelete, requestToMake.toPodId) | 191 | RequestToPod.removePodOf.call(self, requestIdsToDelete, requestToMake.toPod.id) |
195 | return callbackEach() | 192 | return callbackEach() |
196 | } | 193 | } |
197 | 194 | ||
198 | makeRequest(toPod, requestToMake.endpoint, requestToMake.datas, function (success) { | 195 | makeRequest(toPod, requestToMake.endpoint, requestToMake.datas, function (success) { |
199 | if (success === true) { | 196 | if (success === true) { |
200 | logger.debug('Removing requests for %s pod.', requestToMake.toPodId, { requestsIds: requestToMake.ids }) | 197 | logger.debug('Removing requests for pod %s.', requestToMake.toPod.id, { requestsIds: requestToMake.ids }) |
201 | 198 | ||
202 | goodPods.push(requestToMake.toPodId) | 199 | goodPods.push(requestToMake.toPod.id) |
203 | 200 | ||
204 | // Remove the pod id of these request ids | 201 | // Remove the pod id of these request ids |
205 | removePodOf.call(self, requestToMake.ids, requestToMake.toPodId, callbackEach) | 202 | RequestToPod.removePodOf(requestToMake.ids, requestToMake.toPod.id, callbackEach) |
206 | } else { | 203 | } else { |
207 | badPods.push(requestToMake.toPodId) | 204 | badPods.push(requestToMake.toPod.id) |
208 | callbackEach() | 205 | callbackEach() |
209 | } | 206 | } |
210 | }) | ||
211 | }) | 207 | }) |
212 | }, function () { | 208 | }, function () { |
213 | // All the requests were made, we update the pods score | 209 | // All the requests were made, we update the pods score |
214 | updatePodsScore(goodPods, badPods) | 210 | updatePodsScore.call(self, goodPods, badPods) |
215 | // Flush requests with no pod | 211 | // Flush requests with no pod |
216 | removeWithEmptyTo.call(self) | 212 | removeWithEmptyTo.call(self, function (err) { |
213 | if (err) logger.error('Error when removing requests with no pods.', { error: err }) | ||
214 | }) | ||
217 | }) | 215 | }) |
218 | }) | 216 | }) |
219 | } | 217 | } |
220 | 218 | ||
221 | // Remove pods with a score of 0 (too many requests where they were unreachable) | 219 | // Remove pods with a score of 0 (too many requests where they were unreachable) |
222 | function removeBadPods () { | 220 | function removeBadPods () { |
221 | const self = this | ||
222 | |||
223 | waterfall([ | 223 | waterfall([ |
224 | function findBadPods (callback) { | 224 | function findBadPods (callback) { |
225 | Pod.listBadPods(function (err, pods) { | 225 | self.sequelize.models.Pod.listBadPods(function (err, pods) { |
226 | if (err) { | 226 | if (err) { |
227 | logger.error('Cannot find bad pods.', { error: err }) | 227 | logger.error('Cannot find bad pods.', { error: err }) |
228 | return callback(err) | 228 | return callback(err) |
@@ -233,10 +233,8 @@ function removeBadPods () { | |||
233 | }, | 233 | }, |
234 | 234 | ||
235 | function removeTheseBadPods (pods, callback) { | 235 | function removeTheseBadPods (pods, callback) { |
236 | if (pods.length === 0) return callback(null, 0) | ||
237 | |||
238 | each(pods, function (pod, callbackEach) { | 236 | each(pods, function (pod, callbackEach) { |
239 | pod.remove(callbackEach) | 237 | pod.destroy().asCallback(callbackEach) |
240 | }, function (err) { | 238 | }, function (err) { |
241 | return callback(err, pods.length) | 239 | return callback(err, pods.length) |
242 | }) | 240 | }) |
@@ -253,43 +251,98 @@ function removeBadPods () { | |||
253 | } | 251 | } |
254 | 252 | ||
255 | function updatePodsScore (goodPods, badPods) { | 253 | function updatePodsScore (goodPods, badPods) { |
254 | const self = this | ||
255 | const Pod = this.sequelize.models.Pod | ||
256 | |||
256 | logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) | 257 | logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) |
257 | 258 | ||
258 | Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) { | 259 | if (goodPods.length !== 0) { |
259 | if (err) logger.error('Cannot increment scores of good pods.') | 260 | Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) { |
260 | }) | 261 | if (err) logger.error('Cannot increment scores of good pods.', { error: err }) |
262 | }) | ||
263 | } | ||
261 | 264 | ||
262 | Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) { | 265 | if (badPods.length !== 0) { |
263 | if (err) logger.error('Cannot decrement scores of bad pods.') | 266 | Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) { |
264 | removeBadPods() | 267 | if (err) logger.error('Cannot decrement scores of bad pods.', { error: err }) |
265 | }) | 268 | removeBadPods.call(self) |
269 | }) | ||
270 | } | ||
266 | } | 271 | } |
267 | 272 | ||
268 | function listWithLimitAndRandom (limit, callback) { | 273 | function listWithLimitAndRandom (limitPods, limitRequestsPerPod, callback) { |
269 | const self = this | 274 | const self = this |
275 | const Pod = this.sequelize.models.Pod | ||
270 | 276 | ||
271 | self.count(function (err, count) { | 277 | Pod.listRandomPodIdsWithRequest(limitPods, function (err, podIds) { |
272 | if (err) return callback(err) | 278 | if (err) return callback(err) |
273 | 279 | ||
274 | let start = Math.floor(Math.random() * count) - limit | 280 | // We don't have friends that have requests |
275 | if (start < 0) start = 0 | 281 | if (podIds.length === 0) return callback(null, []) |
282 | |||
283 | // The the first x requests of these pods | ||
284 | // It is very important to sort by id ASC to keep the requests order! | ||
285 | const query = { | ||
286 | order: [ | ||
287 | [ 'id', 'ASC' ] | ||
288 | ], | ||
289 | include: [ | ||
290 | { | ||
291 | model: self.sequelize.models.Pod, | ||
292 | where: { | ||
293 | id: { | ||
294 | $in: podIds | ||
295 | } | ||
296 | } | ||
297 | } | ||
298 | ] | ||
299 | } | ||
300 | |||
301 | self.findAll(query).asCallback(function (err, requests) { | ||
302 | if (err) return callback(err) | ||
276 | 303 | ||
277 | self.find().sort({ _id: 1 }).skip(start).limit(limit).exec(callback) | 304 | const requestsGrouped = groupAndTruncateRequests(requests, limitRequestsPerPod) |
305 | return callback(err, requestsGrouped) | ||
306 | }) | ||
278 | }) | 307 | }) |
279 | } | 308 | } |
280 | 309 | ||
281 | function removeAll (callback) { | 310 | function groupAndTruncateRequests (requests, limitRequestsPerPod) { |
282 | this.remove({ }, callback) | 311 | const requestsGrouped = {} |
283 | } | ||
284 | 312 | ||
285 | function removePodOf (requestsIds, podId, callback) { | 313 | requests.forEach(function (request) { |
286 | if (!callback) callback = function () {} | 314 | request.Pods.forEach(function (pod) { |
315 | if (!requestsGrouped[pod.id]) requestsGrouped[pod.id] = [] | ||
287 | 316 | ||
288 | this.update({ _id: { $in: requestsIds } }, { $pull: { to: podId } }, { multi: true }, callback) | 317 | if (requestsGrouped[pod.id].length < limitRequestsPerPod) { |
318 | requestsGrouped[pod.id].push({ | ||
319 | request, | ||
320 | pod | ||
321 | }) | ||
322 | } | ||
323 | }) | ||
324 | }) | ||
325 | |||
326 | return requestsGrouped | ||
327 | } | ||
328 | |||
329 | function removeAll (callback) { | ||
330 | // Delete all requests | ||
331 | this.truncate({ cascade: true }).asCallback(callback) | ||
289 | } | 332 | } |
290 | 333 | ||
291 | function removeWithEmptyTo (callback) { | 334 | function removeWithEmptyTo (callback) { |
292 | if (!callback) callback = function () {} | 335 | if (!callback) callback = function () {} |
293 | 336 | ||
294 | this.remove({ to: { $size: 0 } }, callback) | 337 | const query = { |
338 | where: { | ||
339 | id: { | ||
340 | $notIn: [ | ||
341 | this.sequelize.literal('SELECT "requestId" FROM "RequestToPods"') | ||
342 | ] | ||
343 | } | ||
344 | } | ||
345 | } | ||
346 | |||
347 | this.destroy(query).asCallback(callback) | ||
295 | } | 348 | } |
diff --git a/server/models/tag.js b/server/models/tag.js new file mode 100644 index 000000000..145e090c1 --- /dev/null +++ b/server/models/tag.js | |||
@@ -0,0 +1,76 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const each = require('async/each') | ||
4 | |||
5 | // --------------------------------------------------------------------------- | ||
6 | |||
7 | module.exports = function (sequelize, DataTypes) { | ||
8 | const Tag = sequelize.define('Tag', | ||
9 | { | ||
10 | name: { | ||
11 | type: DataTypes.STRING, | ||
12 | allowNull: false | ||
13 | } | ||
14 | }, | ||
15 | { | ||
16 | timestamps: false, | ||
17 | indexes: [ | ||
18 | { | ||
19 | fields: [ 'name' ], | ||
20 | unique: true | ||
21 | } | ||
22 | ], | ||
23 | classMethods: { | ||
24 | associate, | ||
25 | |||
26 | findOrCreateTags | ||
27 | } | ||
28 | } | ||
29 | ) | ||
30 | |||
31 | return Tag | ||
32 | } | ||
33 | |||
34 | // --------------------------------------------------------------------------- | ||
35 | |||
36 | function associate (models) { | ||
37 | this.belongsToMany(models.Video, { | ||
38 | foreignKey: 'tagId', | ||
39 | through: models.VideoTag, | ||
40 | onDelete: 'cascade' | ||
41 | }) | ||
42 | } | ||
43 | |||
44 | function findOrCreateTags (tags, transaction, callback) { | ||
45 | if (!callback) { | ||
46 | callback = transaction | ||
47 | transaction = null | ||
48 | } | ||
49 | |||
50 | const self = this | ||
51 | const tagInstances = [] | ||
52 | |||
53 | each(tags, function (tag, callbackEach) { | ||
54 | const query = { | ||
55 | where: { | ||
56 | name: tag | ||
57 | }, | ||
58 | defaults: { | ||
59 | name: tag | ||
60 | } | ||
61 | } | ||
62 | |||
63 | if (transaction) query.transaction = transaction | ||
64 | |||
65 | self.findOrCreate(query).asCallback(function (err, res) { | ||
66 | if (err) return callbackEach(err) | ||
67 | |||
68 | // res = [ tag, isCreated ] | ||
69 | const tag = res[0] | ||
70 | tagInstances.push(tag) | ||
71 | return callbackEach() | ||
72 | }) | ||
73 | }, function (err) { | ||
74 | return callback(err, tagInstances) | ||
75 | }) | ||
76 | } | ||
diff --git a/server/models/user.js b/server/models/user.js index a19de7072..36ed723cc 100644 --- a/server/models/user.js +++ b/server/models/user.js | |||
@@ -1,60 +1,81 @@ | |||
1 | const mongoose = require('mongoose') | 1 | 'use strict' |
2 | |||
3 | const values = require('lodash/values') | ||
2 | 4 | ||
3 | const customUsersValidators = require('../helpers/custom-validators').users | ||
4 | const modelUtils = require('./utils') | 5 | const modelUtils = require('./utils') |
6 | const constants = require('../initializers/constants') | ||
5 | const peertubeCrypto = require('../helpers/peertube-crypto') | 7 | const peertubeCrypto = require('../helpers/peertube-crypto') |
6 | 8 | const customUsersValidators = require('../helpers/custom-validators').users | |
7 | const OAuthToken = mongoose.model('OAuthToken') | ||
8 | 9 | ||
9 | // --------------------------------------------------------------------------- | 10 | // --------------------------------------------------------------------------- |
10 | 11 | ||
11 | const UserSchema = mongoose.Schema({ | 12 | module.exports = function (sequelize, DataTypes) { |
12 | createdDate: { | 13 | const User = sequelize.define('User', |
13 | type: Date, | 14 | { |
14 | default: Date.now | 15 | password: { |
15 | }, | 16 | type: DataTypes.STRING, |
16 | password: String, | 17 | allowNull: false, |
17 | username: String, | 18 | validate: { |
18 | role: String | 19 | passwordValid: function (value) { |
19 | }) | 20 | const res = customUsersValidators.isUserPasswordValid(value) |
20 | 21 | if (res === false) throw new Error('Password not valid.') | |
21 | UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) | 22 | } |
22 | UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) | 23 | } |
23 | UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) | 24 | }, |
24 | 25 | username: { | |
25 | UserSchema.methods = { | 26 | type: DataTypes.STRING, |
26 | isPasswordMatch, | 27 | allowNull: false, |
27 | toFormatedJSON | 28 | validate: { |
28 | } | 29 | usernameValid: function (value) { |
29 | 30 | const res = customUsersValidators.isUserUsernameValid(value) | |
30 | UserSchema.statics = { | 31 | if (res === false) throw new Error('Username not valid.') |
31 | countTotal, | 32 | } |
32 | getByUsername, | 33 | } |
33 | list, | 34 | }, |
34 | listForApi, | 35 | role: { |
35 | loadById, | 36 | type: DataTypes.ENUM(values(constants.USER_ROLES)), |
36 | loadByUsername | 37 | allowNull: false |
38 | } | ||
39 | }, | ||
40 | { | ||
41 | indexes: [ | ||
42 | { | ||
43 | fields: [ 'username' ] | ||
44 | } | ||
45 | ], | ||
46 | classMethods: { | ||
47 | associate, | ||
48 | |||
49 | countTotal, | ||
50 | getByUsername, | ||
51 | list, | ||
52 | listForApi, | ||
53 | loadById, | ||
54 | loadByUsername | ||
55 | }, | ||
56 | instanceMethods: { | ||
57 | isPasswordMatch, | ||
58 | toFormatedJSON | ||
59 | }, | ||
60 | hooks: { | ||
61 | beforeCreate: beforeCreateOrUpdate, | ||
62 | beforeUpdate: beforeCreateOrUpdate | ||
63 | } | ||
64 | } | ||
65 | ) | ||
66 | |||
67 | return User | ||
37 | } | 68 | } |
38 | 69 | ||
39 | UserSchema.pre('save', function (next) { | 70 | function beforeCreateOrUpdate (user, options, next) { |
40 | const user = this | 71 | peertubeCrypto.cryptPassword(user.password, function (err, hash) { |
41 | |||
42 | peertubeCrypto.cryptPassword(this.password, function (err, hash) { | ||
43 | if (err) return next(err) | 72 | if (err) return next(err) |
44 | 73 | ||
45 | user.password = hash | 74 | user.password = hash |
46 | 75 | ||
47 | return next() | 76 | return next() |
48 | }) | 77 | }) |
49 | }) | 78 | } |
50 | |||
51 | UserSchema.pre('remove', function (next) { | ||
52 | const user = this | ||
53 | |||
54 | OAuthToken.removeByUserId(user._id, next) | ||
55 | }) | ||
56 | |||
57 | mongoose.model('User', UserSchema) | ||
58 | 79 | ||
59 | // ------------------------------ METHODS ------------------------------ | 80 | // ------------------------------ METHODS ------------------------------ |
60 | 81 | ||
@@ -64,35 +85,68 @@ function isPasswordMatch (password, callback) { | |||
64 | 85 | ||
65 | function toFormatedJSON () { | 86 | function toFormatedJSON () { |
66 | return { | 87 | return { |
67 | id: this._id, | 88 | id: this.id, |
68 | username: this.username, | 89 | username: this.username, |
69 | role: this.role, | 90 | role: this.role, |
70 | createdDate: this.createdDate | 91 | createdAt: this.createdAt |
71 | } | 92 | } |
72 | } | 93 | } |
73 | // ------------------------------ STATICS ------------------------------ | 94 | // ------------------------------ STATICS ------------------------------ |
74 | 95 | ||
96 | function associate (models) { | ||
97 | this.hasOne(models.Author, { | ||
98 | foreignKey: 'userId', | ||
99 | onDelete: 'cascade' | ||
100 | }) | ||
101 | |||
102 | this.hasMany(models.OAuthToken, { | ||
103 | foreignKey: 'userId', | ||
104 | onDelete: 'cascade' | ||
105 | }) | ||
106 | } | ||
107 | |||
75 | function countTotal (callback) { | 108 | function countTotal (callback) { |
76 | return this.count(callback) | 109 | return this.count().asCallback(callback) |
77 | } | 110 | } |
78 | 111 | ||
79 | function getByUsername (username) { | 112 | function getByUsername (username) { |
80 | return this.findOne({ username: username }) | 113 | const query = { |
114 | where: { | ||
115 | username: username | ||
116 | } | ||
117 | } | ||
118 | |||
119 | return this.findOne(query) | ||
81 | } | 120 | } |
82 | 121 | ||
83 | function list (callback) { | 122 | function list (callback) { |
84 | return this.find(callback) | 123 | return this.find().asCallback(callback) |
85 | } | 124 | } |
86 | 125 | ||
87 | function listForApi (start, count, sort, callback) { | 126 | function listForApi (start, count, sort, callback) { |
88 | const query = {} | 127 | const query = { |
89 | return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) | 128 | offset: start, |
129 | limit: count, | ||
130 | order: [ modelUtils.getSort(sort) ] | ||
131 | } | ||
132 | |||
133 | return this.findAndCountAll(query).asCallback(function (err, result) { | ||
134 | if (err) return callback(err) | ||
135 | |||
136 | return callback(null, result.rows, result.count) | ||
137 | }) | ||
90 | } | 138 | } |
91 | 139 | ||
92 | function loadById (id, callback) { | 140 | function loadById (id, callback) { |
93 | return this.findById(id, callback) | 141 | return this.findById(id).asCallback(callback) |
94 | } | 142 | } |
95 | 143 | ||
96 | function loadByUsername (username, callback) { | 144 | function loadByUsername (username, callback) { |
97 | return this.findOne({ username: username }, callback) | 145 | const query = { |
146 | where: { | ||
147 | username: username | ||
148 | } | ||
149 | } | ||
150 | |||
151 | return this.findOne(query).asCallback(callback) | ||
98 | } | 152 | } |
diff --git a/server/models/utils.js b/server/models/utils.js index e798aabe6..49636b3d8 100644 --- a/server/models/utils.js +++ b/server/models/utils.js | |||
@@ -1,28 +1,23 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const parallel = require('async/parallel') | ||
4 | |||
5 | const utils = { | 3 | const utils = { |
6 | listForApiWithCount | 4 | getSort |
7 | } | 5 | } |
8 | 6 | ||
9 | function listForApiWithCount (query, start, count, sort, callback) { | 7 | // Translate for example "-name" to [ 'name', 'DESC' ] |
10 | const self = this | 8 | function getSort (value) { |
9 | let field | ||
10 | let direction | ||
11 | 11 | ||
12 | parallel([ | 12 | if (value.substring(0, 1) === '-') { |
13 | function (asyncCallback) { | 13 | direction = 'DESC' |
14 | self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback) | 14 | field = value.substring(1) |
15 | }, | 15 | } else { |
16 | function (asyncCallback) { | 16 | direction = 'ASC' |
17 | self.count(query, asyncCallback) | 17 | field = value |
18 | } | 18 | } |
19 | ], function (err, results) { | ||
20 | if (err) return callback(err) | ||
21 | 19 | ||
22 | const data = results[0] | 20 | return [ field, direction ] |
23 | const total = results[1] | ||
24 | return callback(null, data, total) | ||
25 | }) | ||
26 | } | 21 | } |
27 | 22 | ||
28 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
diff --git a/server/models/video-abuse.js b/server/models/video-abuse.js new file mode 100644 index 000000000..766a7568d --- /dev/null +++ b/server/models/video-abuse.js | |||
@@ -0,0 +1,113 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const constants = require('../initializers/constants') | ||
4 | const modelUtils = require('./utils') | ||
5 | const customVideosValidators = require('../helpers/custom-validators').videos | ||
6 | |||
7 | module.exports = function (sequelize, DataTypes) { | ||
8 | const VideoAbuse = sequelize.define('VideoAbuse', | ||
9 | { | ||
10 | reporterUsername: { | ||
11 | type: DataTypes.STRING, | ||
12 | allowNull: false, | ||
13 | validate: { | ||
14 | reporterUsernameValid: function (value) { | ||
15 | const res = customVideosValidators.isVideoAbuseReporterUsernameValid(value) | ||
16 | if (res === false) throw new Error('Video abuse reporter username is not valid.') | ||
17 | } | ||
18 | } | ||
19 | }, | ||
20 | reason: { | ||
21 | type: DataTypes.STRING, | ||
22 | allowNull: false, | ||
23 | validate: { | ||
24 | reasonValid: function (value) { | ||
25 | const res = customVideosValidators.isVideoAbuseReasonValid(value) | ||
26 | if (res === false) throw new Error('Video abuse reason is not valid.') | ||
27 | } | ||
28 | } | ||
29 | } | ||
30 | }, | ||
31 | { | ||
32 | indexes: [ | ||
33 | { | ||
34 | fields: [ 'videoId' ] | ||
35 | }, | ||
36 | { | ||
37 | fields: [ 'reporterPodId' ] | ||
38 | } | ||
39 | ], | ||
40 | classMethods: { | ||
41 | associate, | ||
42 | |||
43 | listForApi | ||
44 | }, | ||
45 | instanceMethods: { | ||
46 | toFormatedJSON | ||
47 | } | ||
48 | } | ||
49 | ) | ||
50 | |||
51 | return VideoAbuse | ||
52 | } | ||
53 | |||
54 | // --------------------------------------------------------------------------- | ||
55 | |||
56 | function associate (models) { | ||
57 | this.belongsTo(models.Pod, { | ||
58 | foreignKey: { | ||
59 | name: 'reporterPodId', | ||
60 | allowNull: true | ||
61 | }, | ||
62 | onDelete: 'cascade' | ||
63 | }) | ||
64 | |||
65 | this.belongsTo(models.Video, { | ||
66 | foreignKey: { | ||
67 | name: 'videoId', | ||
68 | allowNull: false | ||
69 | }, | ||
70 | onDelete: 'cascade' | ||
71 | }) | ||
72 | } | ||
73 | |||
74 | function listForApi (start, count, sort, callback) { | ||
75 | const query = { | ||
76 | offset: start, | ||
77 | limit: count, | ||
78 | order: [ modelUtils.getSort(sort) ], | ||
79 | include: [ | ||
80 | { | ||
81 | model: this.sequelize.models.Pod, | ||
82 | required: false | ||
83 | } | ||
84 | ] | ||
85 | } | ||
86 | |||
87 | return this.findAndCountAll(query).asCallback(function (err, result) { | ||
88 | if (err) return callback(err) | ||
89 | |||
90 | return callback(null, result.rows, result.count) | ||
91 | }) | ||
92 | } | ||
93 | |||
94 | function toFormatedJSON () { | ||
95 | let reporterPodHost | ||
96 | |||
97 | if (this.Pod) { | ||
98 | reporterPodHost = this.Pod.host | ||
99 | } else { | ||
100 | // It means it's our video | ||
101 | reporterPodHost = constants.CONFIG.WEBSERVER.HOST | ||
102 | } | ||
103 | |||
104 | const json = { | ||
105 | id: this.id, | ||
106 | reporterPodHost, | ||
107 | reason: this.reason, | ||
108 | reporterUsername: this.reporterUsername, | ||
109 | videoId: this.videoId | ||
110 | } | ||
111 | |||
112 | return json | ||
113 | } | ||
diff --git a/server/models/video-tag.js b/server/models/video-tag.js new file mode 100644 index 000000000..cd9277a6e --- /dev/null +++ b/server/models/video-tag.js | |||
@@ -0,0 +1,18 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | // --------------------------------------------------------------------------- | ||
4 | |||
5 | module.exports = function (sequelize, DataTypes) { | ||
6 | const VideoTag = sequelize.define('VideoTag', {}, { | ||
7 | indexes: [ | ||
8 | { | ||
9 | fields: [ 'videoId' ] | ||
10 | }, | ||
11 | { | ||
12 | fields: [ 'tagId' ] | ||
13 | } | ||
14 | ] | ||
15 | }) | ||
16 | |||
17 | return VideoTag | ||
18 | } | ||
diff --git a/server/models/video.js b/server/models/video.js index 330067cdf..17eff6428 100644 --- a/server/models/video.js +++ b/server/models/video.js | |||
@@ -1,108 +1,160 @@ | |||
1 | 'use strict' | 1 | 'use strict' |
2 | 2 | ||
3 | const Buffer = require('safe-buffer').Buffer | ||
3 | const createTorrent = require('create-torrent') | 4 | const createTorrent = require('create-torrent') |
4 | const ffmpeg = require('fluent-ffmpeg') | 5 | const ffmpeg = require('fluent-ffmpeg') |
5 | const fs = require('fs') | 6 | const fs = require('fs') |
6 | const magnetUtil = require('magnet-uri') | 7 | const magnetUtil = require('magnet-uri') |
8 | const map = require('lodash/map') | ||
7 | const parallel = require('async/parallel') | 9 | const parallel = require('async/parallel') |
8 | const parseTorrent = require('parse-torrent') | 10 | const parseTorrent = require('parse-torrent') |
9 | const pathUtils = require('path') | 11 | const pathUtils = require('path') |
10 | const mongoose = require('mongoose') | 12 | const values = require('lodash/values') |
11 | 13 | ||
12 | const constants = require('../initializers/constants') | 14 | const constants = require('../initializers/constants') |
13 | const customVideosValidators = require('../helpers/custom-validators').videos | ||
14 | const logger = require('../helpers/logger') | 15 | const logger = require('../helpers/logger') |
16 | const friends = require('../lib/friends') | ||
15 | const modelUtils = require('./utils') | 17 | const modelUtils = require('./utils') |
18 | const customVideosValidators = require('../helpers/custom-validators').videos | ||
16 | 19 | ||
17 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
18 | 21 | ||
19 | // TODO: add indexes on searchable columns | 22 | module.exports = function (sequelize, DataTypes) { |
20 | const VideoSchema = mongoose.Schema({ | 23 | const Video = sequelize.define('Video', |
21 | name: String, | 24 | { |
22 | extname: { | 25 | id: { |
23 | type: String, | 26 | type: DataTypes.UUID, |
24 | enum: [ '.mp4', '.webm', '.ogv' ] | 27 | defaultValue: DataTypes.UUIDV4, |
25 | }, | 28 | primaryKey: true, |
26 | remoteId: mongoose.Schema.Types.ObjectId, | 29 | validate: { |
27 | description: String, | 30 | isUUID: 4 |
28 | magnet: { | 31 | } |
29 | infoHash: String | ||
30 | }, | ||
31 | podHost: String, | ||
32 | author: String, | ||
33 | duration: Number, | ||
34 | tags: [ String ], | ||
35 | createdDate: { | ||
36 | type: Date, | ||
37 | default: Date.now | ||
38 | } | ||
39 | }) | ||
40 | |||
41 | VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid) | ||
42 | VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid) | ||
43 | VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid) | ||
44 | VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid) | ||
45 | VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid) | ||
46 | VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid) | ||
47 | |||
48 | VideoSchema.methods = { | ||
49 | generateMagnetUri, | ||
50 | getVideoFilename, | ||
51 | getThumbnailName, | ||
52 | getPreviewName, | ||
53 | getTorrentName, | ||
54 | isOwned, | ||
55 | toFormatedJSON, | ||
56 | toRemoteJSON | ||
57 | } | ||
58 | |||
59 | VideoSchema.statics = { | ||
60 | generateThumbnailFromBase64, | ||
61 | getDurationFromFile, | ||
62 | listForApi, | ||
63 | listByHostAndRemoteId, | ||
64 | listByHost, | ||
65 | listOwned, | ||
66 | listOwnedByAuthor, | ||
67 | listRemotes, | ||
68 | load, | ||
69 | search | ||
70 | } | ||
71 | |||
72 | VideoSchema.pre('remove', function (next) { | ||
73 | const video = this | ||
74 | const tasks = [] | ||
75 | |||
76 | tasks.push( | ||
77 | function (callback) { | ||
78 | removeThumbnail(video, callback) | ||
79 | } | ||
80 | ) | ||
81 | |||
82 | if (video.isOwned()) { | ||
83 | tasks.push( | ||
84 | function (callback) { | ||
85 | removeFile(video, callback) | ||
86 | }, | 32 | }, |
87 | function (callback) { | 33 | name: { |
88 | removeTorrent(video, callback) | 34 | type: DataTypes.STRING, |
35 | allowNull: false, | ||
36 | validate: { | ||
37 | nameValid: function (value) { | ||
38 | const res = customVideosValidators.isVideoNameValid(value) | ||
39 | if (res === false) throw new Error('Video name is not valid.') | ||
40 | } | ||
41 | } | ||
89 | }, | 42 | }, |
90 | function (callback) { | 43 | extname: { |
91 | removePreview(video, callback) | 44 | type: DataTypes.ENUM(values(constants.CONSTRAINTS_FIELDS.VIDEOS.EXTNAME)), |
45 | allowNull: false | ||
46 | }, | ||
47 | remoteId: { | ||
48 | type: DataTypes.UUID, | ||
49 | allowNull: true, | ||
50 | validate: { | ||
51 | isUUID: 4 | ||
52 | } | ||
53 | }, | ||
54 | description: { | ||
55 | type: DataTypes.STRING, | ||
56 | allowNull: false, | ||
57 | validate: { | ||
58 | descriptionValid: function (value) { | ||
59 | const res = customVideosValidators.isVideoDescriptionValid(value) | ||
60 | if (res === false) throw new Error('Video description is not valid.') | ||
61 | } | ||
62 | } | ||
63 | }, | ||
64 | infoHash: { | ||
65 | type: DataTypes.STRING, | ||
66 | allowNull: false, | ||
67 | validate: { | ||
68 | infoHashValid: function (value) { | ||
69 | const res = customVideosValidators.isVideoInfoHashValid(value) | ||
70 | if (res === false) throw new Error('Video info hash is not valid.') | ||
71 | } | ||
72 | } | ||
73 | }, | ||
74 | duration: { | ||
75 | type: DataTypes.INTEGER, | ||
76 | allowNull: false, | ||
77 | validate: { | ||
78 | durationValid: function (value) { | ||
79 | const res = customVideosValidators.isVideoDurationValid(value) | ||
80 | if (res === false) throw new Error('Video duration is not valid.') | ||
81 | } | ||
82 | } | ||
92 | } | 83 | } |
93 | ) | 84 | }, |
85 | { | ||
86 | indexes: [ | ||
87 | { | ||
88 | fields: [ 'authorId' ] | ||
89 | }, | ||
90 | { | ||
91 | fields: [ 'remoteId' ] | ||
92 | }, | ||
93 | { | ||
94 | fields: [ 'name' ] | ||
95 | }, | ||
96 | { | ||
97 | fields: [ 'createdAt' ] | ||
98 | }, | ||
99 | { | ||
100 | fields: [ 'duration' ] | ||
101 | }, | ||
102 | { | ||
103 | fields: [ 'infoHash' ] | ||
104 | } | ||
105 | ], | ||
106 | classMethods: { | ||
107 | associate, | ||
108 | |||
109 | generateThumbnailFromData, | ||
110 | getDurationFromFile, | ||
111 | list, | ||
112 | listForApi, | ||
113 | listOwnedAndPopulateAuthorAndTags, | ||
114 | listOwnedByAuthor, | ||
115 | load, | ||
116 | loadByHostAndRemoteId, | ||
117 | loadAndPopulateAuthor, | ||
118 | loadAndPopulateAuthorAndPodAndTags, | ||
119 | searchAndPopulateAuthorAndPodAndTags | ||
120 | }, | ||
121 | instanceMethods: { | ||
122 | generateMagnetUri, | ||
123 | getVideoFilename, | ||
124 | getThumbnailName, | ||
125 | getPreviewName, | ||
126 | getTorrentName, | ||
127 | isOwned, | ||
128 | toFormatedJSON, | ||
129 | toAddRemoteJSON, | ||
130 | toUpdateRemoteJSON | ||
131 | }, | ||
132 | hooks: { | ||
133 | beforeValidate, | ||
134 | beforeCreate, | ||
135 | afterDestroy | ||
136 | } | ||
137 | } | ||
138 | ) | ||
139 | |||
140 | return Video | ||
141 | } | ||
142 | |||
143 | function beforeValidate (video, options, next) { | ||
144 | // Put a fake infoHash if it does not exists yet | ||
145 | if (video.isOwned() && !video.infoHash) { | ||
146 | // 40 hexa length | ||
147 | video.infoHash = '0123456789abcdef0123456789abcdef01234567' | ||
94 | } | 148 | } |
95 | 149 | ||
96 | parallel(tasks, next) | 150 | return next(null) |
97 | }) | 151 | } |
98 | 152 | ||
99 | VideoSchema.pre('save', function (next) { | 153 | function beforeCreate (video, options, next) { |
100 | const video = this | ||
101 | const tasks = [] | 154 | const tasks = [] |
102 | 155 | ||
103 | if (video.isOwned()) { | 156 | if (video.isOwned()) { |
104 | const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) | 157 | const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) |
105 | this.podHost = constants.CONFIG.WEBSERVER.HOST | ||
106 | 158 | ||
107 | tasks.push( | 159 | tasks.push( |
108 | // TODO: refractoring | 160 | // TODO: refractoring |
@@ -123,9 +175,8 @@ VideoSchema.pre('save', function (next) { | |||
123 | if (err) return callback(err) | 175 | if (err) return callback(err) |
124 | 176 | ||
125 | const parsedTorrent = parseTorrent(torrent) | 177 | const parsedTorrent = parseTorrent(torrent) |
126 | video.magnet.infoHash = parsedTorrent.infoHash | 178 | video.set('infoHash', parsedTorrent.infoHash) |
127 | 179 | video.validate().asCallback(callback) | |
128 | callback(null) | ||
129 | }) | 180 | }) |
130 | }) | 181 | }) |
131 | }, | 182 | }, |
@@ -141,12 +192,72 @@ VideoSchema.pre('save', function (next) { | |||
141 | } | 192 | } |
142 | 193 | ||
143 | return next() | 194 | return next() |
144 | }) | 195 | } |
196 | |||
197 | function afterDestroy (video, options, next) { | ||
198 | const tasks = [] | ||
145 | 199 | ||
146 | mongoose.model('Video', VideoSchema) | 200 | tasks.push( |
201 | function (callback) { | ||
202 | removeThumbnail(video, callback) | ||
203 | } | ||
204 | ) | ||
205 | |||
206 | if (video.isOwned()) { | ||
207 | tasks.push( | ||
208 | function (callback) { | ||
209 | removeFile(video, callback) | ||
210 | }, | ||
211 | |||
212 | function (callback) { | ||
213 | removeTorrent(video, callback) | ||
214 | }, | ||
215 | |||
216 | function (callback) { | ||
217 | removePreview(video, callback) | ||
218 | }, | ||
219 | |||
220 | function (callback) { | ||
221 | const params = { | ||
222 | remoteId: video.id | ||
223 | } | ||
224 | |||
225 | friends.removeVideoToFriends(params) | ||
226 | |||
227 | return callback() | ||
228 | } | ||
229 | ) | ||
230 | } | ||
231 | |||
232 | parallel(tasks, next) | ||
233 | } | ||
147 | 234 | ||
148 | // ------------------------------ METHODS ------------------------------ | 235 | // ------------------------------ METHODS ------------------------------ |
149 | 236 | ||
237 | function associate (models) { | ||
238 | this.belongsTo(models.Author, { | ||
239 | foreignKey: { | ||
240 | name: 'authorId', | ||
241 | allowNull: false | ||
242 | }, | ||
243 | onDelete: 'cascade' | ||
244 | }) | ||
245 | |||
246 | this.belongsToMany(models.Tag, { | ||
247 | foreignKey: 'videoId', | ||
248 | through: models.VideoTag, | ||
249 | onDelete: 'cascade' | ||
250 | }) | ||
251 | |||
252 | this.hasMany(models.VideoAbuse, { | ||
253 | foreignKey: { | ||
254 | name: 'videoId', | ||
255 | allowNull: false | ||
256 | }, | ||
257 | onDelete: 'cascade' | ||
258 | }) | ||
259 | } | ||
260 | |||
150 | function generateMagnetUri () { | 261 | function generateMagnetUri () { |
151 | let baseUrlHttp, baseUrlWs | 262 | let baseUrlHttp, baseUrlWs |
152 | 263 | ||
@@ -154,8 +265,8 @@ function generateMagnetUri () { | |||
154 | baseUrlHttp = constants.CONFIG.WEBSERVER.URL | 265 | baseUrlHttp = constants.CONFIG.WEBSERVER.URL |
155 | baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT | 266 | baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT |
156 | } else { | 267 | } else { |
157 | baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.podHost | 268 | baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host |
158 | baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.podHost | 269 | baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.Author.Pod.host |
159 | } | 270 | } |
160 | 271 | ||
161 | const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName() | 272 | const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName() |
@@ -166,7 +277,7 @@ function generateMagnetUri () { | |||
166 | xs, | 277 | xs, |
167 | announce, | 278 | announce, |
168 | urlList, | 279 | urlList, |
169 | infoHash: this.magnet.infoHash, | 280 | infoHash: this.infoHash, |
170 | name: this.name | 281 | name: this.name |
171 | } | 282 | } |
172 | 283 | ||
@@ -174,20 +285,20 @@ function generateMagnetUri () { | |||
174 | } | 285 | } |
175 | 286 | ||
176 | function getVideoFilename () { | 287 | function getVideoFilename () { |
177 | if (this.isOwned()) return this._id + this.extname | 288 | if (this.isOwned()) return this.id + this.extname |
178 | 289 | ||
179 | return this.remoteId + this.extname | 290 | return this.remoteId + this.extname |
180 | } | 291 | } |
181 | 292 | ||
182 | function getThumbnailName () { | 293 | function getThumbnailName () { |
183 | // We always have a copy of the thumbnail | 294 | // We always have a copy of the thumbnail |
184 | return this._id + '.jpg' | 295 | return this.id + '.jpg' |
185 | } | 296 | } |
186 | 297 | ||
187 | function getPreviewName () { | 298 | function getPreviewName () { |
188 | const extension = '.jpg' | 299 | const extension = '.jpg' |
189 | 300 | ||
190 | if (this.isOwned()) return this._id + extension | 301 | if (this.isOwned()) return this.id + extension |
191 | 302 | ||
192 | return this.remoteId + extension | 303 | return this.remoteId + extension |
193 | } | 304 | } |
@@ -195,7 +306,7 @@ function getPreviewName () { | |||
195 | function getTorrentName () { | 306 | function getTorrentName () { |
196 | const extension = '.torrent' | 307 | const extension = '.torrent' |
197 | 308 | ||
198 | if (this.isOwned()) return this._id + extension | 309 | if (this.isOwned()) return this.id + extension |
199 | 310 | ||
200 | return this.remoteId + extension | 311 | return this.remoteId + extension |
201 | } | 312 | } |
@@ -205,27 +316,37 @@ function isOwned () { | |||
205 | } | 316 | } |
206 | 317 | ||
207 | function toFormatedJSON () { | 318 | function toFormatedJSON () { |
319 | let podHost | ||
320 | |||
321 | if (this.Author.Pod) { | ||
322 | podHost = this.Author.Pod.host | ||
323 | } else { | ||
324 | // It means it's our video | ||
325 | podHost = constants.CONFIG.WEBSERVER.HOST | ||
326 | } | ||
327 | |||
208 | const json = { | 328 | const json = { |
209 | id: this._id, | 329 | id: this.id, |
210 | name: this.name, | 330 | name: this.name, |
211 | description: this.description, | 331 | description: this.description, |
212 | podHost: this.podHost, | 332 | podHost, |
213 | isLocal: this.isOwned(), | 333 | isLocal: this.isOwned(), |
214 | magnetUri: this.generateMagnetUri(), | 334 | magnetUri: this.generateMagnetUri(), |
215 | author: this.author, | 335 | author: this.Author.name, |
216 | duration: this.duration, | 336 | duration: this.duration, |
217 | tags: this.tags, | 337 | tags: map(this.Tags, 'name'), |
218 | thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(), | 338 | thumbnailPath: pathUtils.join(constants.STATIC_PATHS.THUMBNAILS, this.getThumbnailName()), |
219 | createdDate: this.createdDate | 339 | createdAt: this.createdAt, |
340 | updatedAt: this.updatedAt | ||
220 | } | 341 | } |
221 | 342 | ||
222 | return json | 343 | return json |
223 | } | 344 | } |
224 | 345 | ||
225 | function toRemoteJSON (callback) { | 346 | function toAddRemoteJSON (callback) { |
226 | const self = this | 347 | const self = this |
227 | 348 | ||
228 | // Convert thumbnail to base64 | 349 | // Get thumbnail data to send to the other pod |
229 | const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) | 350 | const thumbnailPath = pathUtils.join(constants.CONFIG.STORAGE.THUMBNAILS_DIR, this.getThumbnailName()) |
230 | fs.readFile(thumbnailPath, function (err, thumbnailData) { | 351 | fs.readFile(thumbnailPath, function (err, thumbnailData) { |
231 | if (err) { | 352 | if (err) { |
@@ -236,13 +357,14 @@ function toRemoteJSON (callback) { | |||
236 | const remoteVideo = { | 357 | const remoteVideo = { |
237 | name: self.name, | 358 | name: self.name, |
238 | description: self.description, | 359 | description: self.description, |
239 | magnet: self.magnet, | 360 | infoHash: self.infoHash, |
240 | remoteId: self._id, | 361 | remoteId: self.id, |
241 | author: self.author, | 362 | author: self.Author.name, |
242 | duration: self.duration, | 363 | duration: self.duration, |
243 | thumbnailBase64: new Buffer(thumbnailData).toString('base64'), | 364 | thumbnailData: thumbnailData.toString('binary'), |
244 | tags: self.tags, | 365 | tags: map(self.Tags, 'name'), |
245 | createdDate: self.createdDate, | 366 | createdAt: self.createdAt, |
367 | updatedAt: self.updatedAt, | ||
246 | extname: self.extname | 368 | extname: self.extname |
247 | } | 369 | } |
248 | 370 | ||
@@ -250,14 +372,31 @@ function toRemoteJSON (callback) { | |||
250 | }) | 372 | }) |
251 | } | 373 | } |
252 | 374 | ||
375 | function toUpdateRemoteJSON (callback) { | ||
376 | const json = { | ||
377 | name: this.name, | ||
378 | description: this.description, | ||
379 | infoHash: this.infoHash, | ||
380 | remoteId: this.id, | ||
381 | author: this.Author.name, | ||
382 | duration: this.duration, | ||
383 | tags: map(this.Tags, 'name'), | ||
384 | createdAt: this.createdAt, | ||
385 | updatedAt: this.updatedAt, | ||
386 | extname: this.extname | ||
387 | } | ||
388 | |||
389 | return json | ||
390 | } | ||
391 | |||
253 | // ------------------------------ STATICS ------------------------------ | 392 | // ------------------------------ STATICS ------------------------------ |
254 | 393 | ||
255 | function generateThumbnailFromBase64 (video, thumbnailData, callback) { | 394 | function generateThumbnailFromData (video, thumbnailData, callback) { |
256 | // Creating the thumbnail for a remote video | 395 | // Creating the thumbnail for a remote video |
257 | 396 | ||
258 | const thumbnailName = video.getThumbnailName() | 397 | const thumbnailName = video.getThumbnailName() |
259 | const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName | 398 | const thumbnailPath = constants.CONFIG.STORAGE.THUMBNAILS_DIR + thumbnailName |
260 | fs.writeFile(thumbnailPath, thumbnailData, { encoding: 'base64' }, function (err) { | 399 | fs.writeFile(thumbnailPath, Buffer.from(thumbnailData, 'binary'), function (err) { |
261 | if (err) return callback(err) | 400 | if (err) return callback(err) |
262 | 401 | ||
263 | return callback(null, thumbnailName) | 402 | return callback(null, thumbnailName) |
@@ -272,51 +411,186 @@ function getDurationFromFile (videoPath, callback) { | |||
272 | }) | 411 | }) |
273 | } | 412 | } |
274 | 413 | ||
275 | function listForApi (start, count, sort, callback) { | 414 | function list (callback) { |
276 | const query = {} | 415 | return this.find().asCallback() |
277 | return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) | ||
278 | } | 416 | } |
279 | 417 | ||
280 | function listByHostAndRemoteId (fromHost, remoteId, callback) { | 418 | function listForApi (start, count, sort, callback) { |
281 | this.find({ podHost: fromHost, remoteId: remoteId }, callback) | 419 | const query = { |
420 | offset: start, | ||
421 | limit: count, | ||
422 | distinct: true, // For the count, a video can have many tags | ||
423 | order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ], | ||
424 | include: [ | ||
425 | { | ||
426 | model: this.sequelize.models.Author, | ||
427 | include: [ { model: this.sequelize.models.Pod, required: false } ] | ||
428 | }, | ||
429 | |||
430 | this.sequelize.models.Tag | ||
431 | ] | ||
432 | } | ||
433 | |||
434 | return this.findAndCountAll(query).asCallback(function (err, result) { | ||
435 | if (err) return callback(err) | ||
436 | |||
437 | return callback(null, result.rows, result.count) | ||
438 | }) | ||
282 | } | 439 | } |
283 | 440 | ||
284 | function listByHost (fromHost, callback) { | 441 | function loadByHostAndRemoteId (fromHost, remoteId, callback) { |
285 | this.find({ podHost: fromHost }, callback) | 442 | const query = { |
443 | where: { | ||
444 | remoteId: remoteId | ||
445 | }, | ||
446 | include: [ | ||
447 | { | ||
448 | model: this.sequelize.models.Author, | ||
449 | include: [ | ||
450 | { | ||
451 | model: this.sequelize.models.Pod, | ||
452 | required: true, | ||
453 | where: { | ||
454 | host: fromHost | ||
455 | } | ||
456 | } | ||
457 | ] | ||
458 | } | ||
459 | ] | ||
460 | } | ||
461 | |||
462 | return this.findOne(query).asCallback(callback) | ||
286 | } | 463 | } |
287 | 464 | ||
288 | function listOwned (callback) { | 465 | function listOwnedAndPopulateAuthorAndTags (callback) { |
289 | // If remoteId is null this is *our* video | 466 | // If remoteId is null this is *our* video |
290 | this.find({ remoteId: null }, callback) | 467 | const query = { |
468 | where: { | ||
469 | remoteId: null | ||
470 | }, | ||
471 | include: [ this.sequelize.models.Author, this.sequelize.models.Tag ] | ||
472 | } | ||
473 | |||
474 | return this.findAll(query).asCallback(callback) | ||
291 | } | 475 | } |
292 | 476 | ||
293 | function listOwnedByAuthor (author, callback) { | 477 | function listOwnedByAuthor (author, callback) { |
294 | this.find({ remoteId: null, author: author }, callback) | 478 | const query = { |
295 | } | 479 | where: { |
480 | remoteId: null | ||
481 | }, | ||
482 | include: [ | ||
483 | { | ||
484 | model: this.sequelize.models.Author, | ||
485 | where: { | ||
486 | name: author | ||
487 | } | ||
488 | } | ||
489 | ] | ||
490 | } | ||
296 | 491 | ||
297 | function listRemotes (callback) { | 492 | return this.findAll(query).asCallback(callback) |
298 | this.find({ remoteId: { $ne: null } }, callback) | ||
299 | } | 493 | } |
300 | 494 | ||
301 | function load (id, callback) { | 495 | function load (id, callback) { |
302 | this.findById(id, callback) | 496 | return this.findById(id).asCallback(callback) |
497 | } | ||
498 | |||
499 | function loadAndPopulateAuthor (id, callback) { | ||
500 | const options = { | ||
501 | include: [ this.sequelize.models.Author ] | ||
502 | } | ||
503 | |||
504 | return this.findById(id, options).asCallback(callback) | ||
505 | } | ||
506 | |||
507 | function loadAndPopulateAuthorAndPodAndTags (id, callback) { | ||
508 | const options = { | ||
509 | include: [ | ||
510 | { | ||
511 | model: this.sequelize.models.Author, | ||
512 | include: [ { model: this.sequelize.models.Pod, required: false } ] | ||
513 | }, | ||
514 | this.sequelize.models.Tag | ||
515 | ] | ||
516 | } | ||
517 | |||
518 | return this.findById(id, options).asCallback(callback) | ||
303 | } | 519 | } |
304 | 520 | ||
305 | function search (value, field, start, count, sort, callback) { | 521 | function searchAndPopulateAuthorAndPodAndTags (value, field, start, count, sort, callback) { |
306 | const query = {} | 522 | const podInclude = { |
523 | model: this.sequelize.models.Pod, | ||
524 | required: false | ||
525 | } | ||
526 | |||
527 | const authorInclude = { | ||
528 | model: this.sequelize.models.Author, | ||
529 | include: [ | ||
530 | podInclude | ||
531 | ] | ||
532 | } | ||
533 | |||
534 | const tagInclude = { | ||
535 | model: this.sequelize.models.Tag | ||
536 | } | ||
537 | |||
538 | const query = { | ||
539 | where: {}, | ||
540 | offset: start, | ||
541 | limit: count, | ||
542 | distinct: true, // For the count, a video can have many tags | ||
543 | order: [ modelUtils.getSort(sort), [ this.sequelize.models.Tag, 'name', 'ASC' ] ] | ||
544 | } | ||
545 | |||
307 | // Make an exact search with the magnet | 546 | // Make an exact search with the magnet |
308 | if (field === 'magnetUri') { | 547 | if (field === 'magnetUri') { |
309 | const infoHash = magnetUtil.decode(value).infoHash | 548 | const infoHash = magnetUtil.decode(value).infoHash |
310 | query.magnet = { | 549 | query.where.infoHash = infoHash |
311 | infoHash | ||
312 | } | ||
313 | } else if (field === 'tags') { | 550 | } else if (field === 'tags') { |
314 | query[field] = value | 551 | const escapedValue = this.sequelize.escape('%' + value + '%') |
552 | query.where = { | ||
553 | id: { | ||
554 | $in: this.sequelize.literal( | ||
555 | '(SELECT "VideoTags"."videoId" FROM "Tags" INNER JOIN "VideoTags" ON "Tags"."id" = "VideoTags"."tagId" WHERE name LIKE ' + escapedValue + ')' | ||
556 | ) | ||
557 | } | ||
558 | } | ||
559 | } else if (field === 'host') { | ||
560 | // FIXME: Include our pod? (not stored in the database) | ||
561 | podInclude.where = { | ||
562 | host: { | ||
563 | $like: '%' + value + '%' | ||
564 | } | ||
565 | } | ||
566 | podInclude.required = true | ||
567 | } else if (field === 'author') { | ||
568 | authorInclude.where = { | ||
569 | name: { | ||
570 | $like: '%' + value + '%' | ||
571 | } | ||
572 | } | ||
573 | |||
574 | // authorInclude.or = true | ||
315 | } else { | 575 | } else { |
316 | query[field] = new RegExp(value, 'i') | 576 | query.where[field] = { |
577 | $like: '%' + value + '%' | ||
578 | } | ||
317 | } | 579 | } |
318 | 580 | ||
319 | modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) | 581 | query.include = [ |
582 | authorInclude, tagInclude | ||
583 | ] | ||
584 | |||
585 | if (tagInclude.where) { | ||
586 | // query.include.push([ this.sequelize.models.Tag ]) | ||
587 | } | ||
588 | |||
589 | return this.findAndCountAll(query).asCallback(function (err, result) { | ||
590 | if (err) return callback(err) | ||
591 | |||
592 | return callback(null, result.rows, result.count) | ||
593 | }) | ||
320 | } | 594 | } |
321 | 595 | ||
322 | // --------------------------------------------------------------------------- | 596 | // --------------------------------------------------------------------------- |
diff --git a/server/tests/api/check-params.js b/server/tests/api/check-params.js deleted file mode 100644 index 444c2fc55..000000000 --- a/server/tests/api/check-params.js +++ /dev/null | |||
@@ -1,764 +0,0 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const chai = require('chai') | ||
4 | const expect = chai.expect | ||
5 | const pathUtils = require('path') | ||
6 | const request = require('supertest') | ||
7 | const series = require('async/series') | ||
8 | |||
9 | const loginUtils = require('../utils/login') | ||
10 | const requestsUtils = require('../utils/requests') | ||
11 | const serversUtils = require('../utils/servers') | ||
12 | const usersUtils = require('../utils/users') | ||
13 | |||
14 | describe('Test parameters validator', function () { | ||
15 | let server = null | ||
16 | let userAccessToken = null | ||
17 | |||
18 | // --------------------------------------------------------------- | ||
19 | |||
20 | before(function (done) { | ||
21 | this.timeout(20000) | ||
22 | |||
23 | series([ | ||
24 | function (next) { | ||
25 | serversUtils.flushTests(next) | ||
26 | }, | ||
27 | function (next) { | ||
28 | serversUtils.runServer(1, function (server1) { | ||
29 | server = server1 | ||
30 | |||
31 | next() | ||
32 | }) | ||
33 | }, | ||
34 | function (next) { | ||
35 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
36 | if (err) throw err | ||
37 | server.accessToken = token | ||
38 | |||
39 | next() | ||
40 | }) | ||
41 | } | ||
42 | ], done) | ||
43 | }) | ||
44 | |||
45 | describe('Of the pods API', function () { | ||
46 | const path = '/api/v1/pods/' | ||
47 | |||
48 | describe('When making friends', function () { | ||
49 | let userAccessToken = null | ||
50 | |||
51 | before(function (done) { | ||
52 | usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () { | ||
53 | server.user = { | ||
54 | username: 'user1', | ||
55 | password: 'password' | ||
56 | } | ||
57 | |||
58 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
59 | if (err) throw err | ||
60 | |||
61 | userAccessToken = accessToken | ||
62 | |||
63 | done() | ||
64 | }) | ||
65 | }) | ||
66 | }) | ||
67 | |||
68 | describe('When making friends', function () { | ||
69 | const body = { | ||
70 | hosts: [ 'localhost:9002' ] | ||
71 | } | ||
72 | |||
73 | it('Should fail without hosts', function (done) { | ||
74 | request(server.url) | ||
75 | .post(path + '/makefriends') | ||
76 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
77 | .set('Accept', 'application/json') | ||
78 | .expect(400, done) | ||
79 | }) | ||
80 | |||
81 | it('Should fail if hosts is not an array', function (done) { | ||
82 | request(server.url) | ||
83 | .post(path + '/makefriends') | ||
84 | .send({ hosts: 'localhost:9002' }) | ||
85 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
86 | .set('Accept', 'application/json') | ||
87 | .expect(400, done) | ||
88 | }) | ||
89 | |||
90 | it('Should fail if the array is not composed by hosts', function (done) { | ||
91 | request(server.url) | ||
92 | .post(path + '/makefriends') | ||
93 | .send({ hosts: [ 'localhost:9002', 'localhost:coucou' ] }) | ||
94 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
95 | .set('Accept', 'application/json') | ||
96 | .expect(400, done) | ||
97 | }) | ||
98 | |||
99 | it('Should fail if the array is composed with http schemes', function (done) { | ||
100 | request(server.url) | ||
101 | .post(path + '/makefriends') | ||
102 | .send({ hosts: [ 'localhost:9002', 'http://localhost:9003' ] }) | ||
103 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
104 | .set('Accept', 'application/json') | ||
105 | .expect(400, done) | ||
106 | }) | ||
107 | |||
108 | it('Should fail if hosts are not unique', function (done) { | ||
109 | request(server.url) | ||
110 | .post(path + '/makefriends') | ||
111 | .send({ urls: [ 'localhost:9002', 'localhost:9002' ] }) | ||
112 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
113 | .set('Accept', 'application/json') | ||
114 | .expect(400, done) | ||
115 | }) | ||
116 | |||
117 | it('Should fail with a invalid token', function (done) { | ||
118 | request(server.url) | ||
119 | .post(path + '/makefriends') | ||
120 | .send(body) | ||
121 | .set('Authorization', 'Bearer faketoken') | ||
122 | .set('Accept', 'application/json') | ||
123 | .expect(401, done) | ||
124 | }) | ||
125 | |||
126 | it('Should fail if the user is not an administrator', function (done) { | ||
127 | request(server.url) | ||
128 | .post(path + '/makefriends') | ||
129 | .send(body) | ||
130 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
131 | .set('Accept', 'application/json') | ||
132 | .expect(403, done) | ||
133 | }) | ||
134 | }) | ||
135 | |||
136 | describe('When quitting friends', function () { | ||
137 | it('Should fail with a invalid token', function (done) { | ||
138 | request(server.url) | ||
139 | .get(path + '/quitfriends') | ||
140 | .query({ start: 'hello' }) | ||
141 | .set('Authorization', 'Bearer faketoken') | ||
142 | .set('Accept', 'application/json') | ||
143 | .expect(401, done) | ||
144 | }) | ||
145 | |||
146 | it('Should fail if the user is not an administrator', function (done) { | ||
147 | request(server.url) | ||
148 | .get(path + '/quitfriends') | ||
149 | .query({ start: 'hello' }) | ||
150 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
151 | .set('Accept', 'application/json') | ||
152 | .expect(403, done) | ||
153 | }) | ||
154 | }) | ||
155 | }) | ||
156 | |||
157 | describe('When adding a pod', function () { | ||
158 | it('Should fail with nothing', function (done) { | ||
159 | const data = {} | ||
160 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
161 | }) | ||
162 | |||
163 | it('Should fail without public key', function (done) { | ||
164 | const data = { | ||
165 | host: 'coucou.com' | ||
166 | } | ||
167 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
168 | }) | ||
169 | |||
170 | it('Should fail without an host', function (done) { | ||
171 | const data = { | ||
172 | publicKey: 'mysuperpublickey' | ||
173 | } | ||
174 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
175 | }) | ||
176 | |||
177 | it('Should fail with an incorrect host', function (done) { | ||
178 | const data = { | ||
179 | host: 'http://coucou.com', | ||
180 | publicKey: 'mysuperpublickey' | ||
181 | } | ||
182 | requestsUtils.makePostBodyRequest(server.url, path, null, data, function () { | ||
183 | data.host = 'http://coucou' | ||
184 | requestsUtils.makePostBodyRequest(server.url, path, null, data, function () { | ||
185 | data.host = 'coucou' | ||
186 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
187 | }) | ||
188 | }) | ||
189 | }) | ||
190 | |||
191 | it('Should succeed with the correct parameters', function (done) { | ||
192 | const data = { | ||
193 | host: 'coucou.com', | ||
194 | publicKey: 'mysuperpublickey' | ||
195 | } | ||
196 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200) | ||
197 | }) | ||
198 | }) | ||
199 | }) | ||
200 | |||
201 | describe('Of the videos API', function () { | ||
202 | const path = '/api/v1/videos/' | ||
203 | |||
204 | describe('When listing a video', function () { | ||
205 | it('Should fail with a bad start pagination', function (done) { | ||
206 | request(server.url) | ||
207 | .get(path) | ||
208 | .query({ start: 'hello' }) | ||
209 | .set('Accept', 'application/json') | ||
210 | .expect(400, done) | ||
211 | }) | ||
212 | |||
213 | it('Should fail with a bad count pagination', function (done) { | ||
214 | request(server.url) | ||
215 | .get(path) | ||
216 | .query({ count: 'hello' }) | ||
217 | .set('Accept', 'application/json') | ||
218 | .expect(400, done) | ||
219 | }) | ||
220 | |||
221 | it('Should fail with an incorrect sort', function (done) { | ||
222 | request(server.url) | ||
223 | .get(path) | ||
224 | .query({ sort: 'hello' }) | ||
225 | .set('Accept', 'application/json') | ||
226 | .expect(400, done) | ||
227 | }) | ||
228 | }) | ||
229 | |||
230 | describe('When searching a video', function () { | ||
231 | it('Should fail with nothing', function (done) { | ||
232 | request(server.url) | ||
233 | .get(pathUtils.join(path, 'search')) | ||
234 | .set('Accept', 'application/json') | ||
235 | .expect(400, done) | ||
236 | }) | ||
237 | |||
238 | it('Should fail with a bad start pagination', function (done) { | ||
239 | request(server.url) | ||
240 | .get(pathUtils.join(path, 'search', 'test')) | ||
241 | .query({ start: 'hello' }) | ||
242 | .set('Accept', 'application/json') | ||
243 | .expect(400, done) | ||
244 | }) | ||
245 | |||
246 | it('Should fail with a bad count pagination', function (done) { | ||
247 | request(server.url) | ||
248 | .get(pathUtils.join(path, 'search', 'test')) | ||
249 | .query({ count: 'hello' }) | ||
250 | .set('Accept', 'application/json') | ||
251 | .expect(400, done) | ||
252 | }) | ||
253 | |||
254 | it('Should fail with an incorrect sort', function (done) { | ||
255 | request(server.url) | ||
256 | .get(pathUtils.join(path, 'search', 'test')) | ||
257 | .query({ sort: 'hello' }) | ||
258 | .set('Accept', 'application/json') | ||
259 | .expect(400, done) | ||
260 | }) | ||
261 | }) | ||
262 | |||
263 | describe('When adding a video', function () { | ||
264 | it('Should fail with nothing', function (done) { | ||
265 | const data = {} | ||
266 | const attach = {} | ||
267 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
268 | }) | ||
269 | |||
270 | it('Should fail without name', function (done) { | ||
271 | const data = { | ||
272 | description: 'my super description', | ||
273 | tags: [ 'tag1', 'tag2' ] | ||
274 | } | ||
275 | const attach = { | ||
276 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
277 | } | ||
278 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
279 | }) | ||
280 | |||
281 | it('Should fail with a long name', function (done) { | ||
282 | const data = { | ||
283 | name: 'My very very very very very very very very very very very very very very very very long name', | ||
284 | description: 'my super description', | ||
285 | tags: [ 'tag1', 'tag2' ] | ||
286 | } | ||
287 | const attach = { | ||
288 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
289 | } | ||
290 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
291 | }) | ||
292 | |||
293 | it('Should fail without description', function (done) { | ||
294 | const data = { | ||
295 | name: 'my super name', | ||
296 | tags: [ 'tag1', 'tag2' ] | ||
297 | } | ||
298 | const attach = { | ||
299 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
300 | } | ||
301 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
302 | }) | ||
303 | |||
304 | it('Should fail with a long description', function (done) { | ||
305 | const data = { | ||
306 | name: 'my super name', | ||
307 | description: 'my super description which is very very very very very very very very very very very very very very' + | ||
308 | 'very very very very very very very very very very very very very very very very very very very very very' + | ||
309 | 'very very very very very very very very very very very very very very very long', | ||
310 | tags: [ 'tag1', 'tag2' ] | ||
311 | } | ||
312 | const attach = { | ||
313 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
314 | } | ||
315 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
316 | }) | ||
317 | |||
318 | it('Should fail without tags', function (done) { | ||
319 | const data = { | ||
320 | name: 'my super name', | ||
321 | description: 'my super description' | ||
322 | } | ||
323 | const attach = { | ||
324 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
325 | } | ||
326 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
327 | }) | ||
328 | |||
329 | it('Should fail with too many tags', function (done) { | ||
330 | const data = { | ||
331 | name: 'my super name', | ||
332 | description: 'my super description', | ||
333 | tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ] | ||
334 | } | ||
335 | const attach = { | ||
336 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
337 | } | ||
338 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
339 | }) | ||
340 | |||
341 | it('Should fail with not enough tags', function (done) { | ||
342 | const data = { | ||
343 | name: 'my super name', | ||
344 | description: 'my super description', | ||
345 | tags: [ ] | ||
346 | } | ||
347 | const attach = { | ||
348 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
349 | } | ||
350 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
351 | }) | ||
352 | |||
353 | it('Should fail with a tag length too low', function (done) { | ||
354 | const data = { | ||
355 | name: 'my super name', | ||
356 | description: 'my super description', | ||
357 | tags: [ 'tag1', 't' ] | ||
358 | } | ||
359 | const attach = { | ||
360 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
361 | } | ||
362 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
363 | }) | ||
364 | |||
365 | it('Should fail with a tag length too big', function (done) { | ||
366 | const data = { | ||
367 | name: 'my super name', | ||
368 | description: 'my super description', | ||
369 | tags: [ 'mysupertagtoolong', 'tag1' ] | ||
370 | } | ||
371 | const attach = { | ||
372 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
373 | } | ||
374 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
375 | }) | ||
376 | |||
377 | it('Should fail with malformed tags', function (done) { | ||
378 | const data = { | ||
379 | name: 'my super name', | ||
380 | description: 'my super description', | ||
381 | tags: [ 'my tag' ] | ||
382 | } | ||
383 | const attach = { | ||
384 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
385 | } | ||
386 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
387 | }) | ||
388 | |||
389 | it('Should fail without an input file', function (done) { | ||
390 | const data = { | ||
391 | name: 'my super name', | ||
392 | description: 'my super description', | ||
393 | tags: [ 'tag1', 'tag2' ] | ||
394 | } | ||
395 | const attach = {} | ||
396 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
397 | }) | ||
398 | |||
399 | it('Should fail without an incorrect input file', function (done) { | ||
400 | const data = { | ||
401 | name: 'my super name', | ||
402 | description: 'my super description', | ||
403 | tags: [ 'tag1', 'tag2' ] | ||
404 | } | ||
405 | const attach = { | ||
406 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short_fake.webm') | ||
407 | } | ||
408 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
409 | }) | ||
410 | |||
411 | it('Should fail with a too big duration', function (done) { | ||
412 | const data = { | ||
413 | name: 'my super name', | ||
414 | description: 'my super description', | ||
415 | tags: [ 'tag1', 'tag2' ] | ||
416 | } | ||
417 | const attach = { | ||
418 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_too_long.webm') | ||
419 | } | ||
420 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
421 | }) | ||
422 | |||
423 | it('Should succeed with the correct parameters', function (done) { | ||
424 | const data = { | ||
425 | name: 'my super name', | ||
426 | description: 'my super description', | ||
427 | tags: [ 'tag1', 'tag2' ] | ||
428 | } | ||
429 | const attach = { | ||
430 | 'videofile': pathUtils.join(__dirname, 'fixtures', 'video_short.webm') | ||
431 | } | ||
432 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () { | ||
433 | attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4') | ||
434 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () { | ||
435 | attach.videofile = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv') | ||
436 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done, 204) | ||
437 | }, false) | ||
438 | }, false) | ||
439 | }) | ||
440 | }) | ||
441 | |||
442 | describe('When getting a video', function () { | ||
443 | it('Should return the list of the videos with nothing', function (done) { | ||
444 | request(server.url) | ||
445 | .get(path) | ||
446 | .set('Accept', 'application/json') | ||
447 | .expect(200) | ||
448 | .expect('Content-Type', /json/) | ||
449 | .end(function (err, res) { | ||
450 | if (err) throw err | ||
451 | |||
452 | expect(res.body.data).to.be.an('array') | ||
453 | expect(res.body.data.length).to.equal(3) | ||
454 | |||
455 | done() | ||
456 | }) | ||
457 | }) | ||
458 | |||
459 | it('Should fail without a mongodb id', function (done) { | ||
460 | request(server.url) | ||
461 | .get(path + 'coucou') | ||
462 | .set('Accept', 'application/json') | ||
463 | .expect(400, done) | ||
464 | }) | ||
465 | |||
466 | it('Should return 404 with an incorrect video', function (done) { | ||
467 | request(server.url) | ||
468 | .get(path + '123456789012345678901234') | ||
469 | .set('Accept', 'application/json') | ||
470 | .expect(404, done) | ||
471 | }) | ||
472 | |||
473 | it('Should succeed with the correct parameters') | ||
474 | }) | ||
475 | |||
476 | describe('When removing a video', function () { | ||
477 | it('Should have 404 with nothing', function (done) { | ||
478 | request(server.url) | ||
479 | .delete(path) | ||
480 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
481 | .expect(400, done) | ||
482 | }) | ||
483 | |||
484 | it('Should fail without a mongodb id', function (done) { | ||
485 | request(server.url) | ||
486 | .delete(path + 'hello') | ||
487 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
488 | .expect(400, done) | ||
489 | }) | ||
490 | |||
491 | it('Should fail with a video which does not exist', function (done) { | ||
492 | request(server.url) | ||
493 | .delete(path + '123456789012345678901234') | ||
494 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
495 | .expect(404, done) | ||
496 | }) | ||
497 | |||
498 | it('Should fail with a video of another user') | ||
499 | |||
500 | it('Should fail with a video of another pod') | ||
501 | |||
502 | it('Should succeed with the correct parameters') | ||
503 | }) | ||
504 | }) | ||
505 | |||
506 | describe('Of the users API', function () { | ||
507 | const path = '/api/v1/users/' | ||
508 | let userId = null | ||
509 | let rootId = null | ||
510 | |||
511 | describe('When listing users', function () { | ||
512 | it('Should fail with a bad start pagination', function (done) { | ||
513 | request(server.url) | ||
514 | .get(path) | ||
515 | .query({ start: 'hello' }) | ||
516 | .set('Accept', 'application/json') | ||
517 | .expect(400, done) | ||
518 | }) | ||
519 | |||
520 | it('Should fail with a bad count pagination', function (done) { | ||
521 | request(server.url) | ||
522 | .get(path) | ||
523 | .query({ count: 'hello' }) | ||
524 | .set('Accept', 'application/json') | ||
525 | .expect(400, done) | ||
526 | }) | ||
527 | |||
528 | it('Should fail with an incorrect sort', function (done) { | ||
529 | request(server.url) | ||
530 | .get(path) | ||
531 | .query({ sort: 'hello' }) | ||
532 | .set('Accept', 'application/json') | ||
533 | .expect(400, done) | ||
534 | }) | ||
535 | }) | ||
536 | |||
537 | describe('When adding a new user', function () { | ||
538 | it('Should fail with a too small username', function (done) { | ||
539 | const data = { | ||
540 | username: 'ji', | ||
541 | password: 'mysuperpassword' | ||
542 | } | ||
543 | |||
544 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
545 | }) | ||
546 | |||
547 | it('Should fail with a too long username', function (done) { | ||
548 | const data = { | ||
549 | username: 'mysuperusernamewhichisverylong', | ||
550 | password: 'mysuperpassword' | ||
551 | } | ||
552 | |||
553 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
554 | }) | ||
555 | |||
556 | it('Should fail with an incorrect username', function (done) { | ||
557 | const data = { | ||
558 | username: 'my username', | ||
559 | password: 'mysuperpassword' | ||
560 | } | ||
561 | |||
562 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
563 | }) | ||
564 | |||
565 | it('Should fail with a too small password', function (done) { | ||
566 | const data = { | ||
567 | username: 'myusername', | ||
568 | password: 'bla' | ||
569 | } | ||
570 | |||
571 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
572 | }) | ||
573 | |||
574 | it('Should fail with a too long password', function (done) { | ||
575 | const data = { | ||
576 | username: 'myusername', | ||
577 | password: 'my super long password which is very very very very very very very very very very very very very very' + | ||
578 | 'very very very very very very very very very very very very very very very veryv very very very very' + | ||
579 | 'very very very very very very very very very very very very very very very very very very very very long' | ||
580 | } | ||
581 | |||
582 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
583 | }) | ||
584 | |||
585 | it('Should fail with an non authenticated user', function (done) { | ||
586 | const data = { | ||
587 | username: 'myusername', | ||
588 | password: 'my super password' | ||
589 | } | ||
590 | |||
591 | requestsUtils.makePostBodyRequest(server.url, path, 'super token', data, done, 401) | ||
592 | }) | ||
593 | |||
594 | it('Should fail if we add a user with the same username', function (done) { | ||
595 | const data = { | ||
596 | username: 'user1', | ||
597 | password: 'my super password' | ||
598 | } | ||
599 | |||
600 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 409) | ||
601 | }) | ||
602 | |||
603 | it('Should succeed with the correct params', function (done) { | ||
604 | const data = { | ||
605 | username: 'user2', | ||
606 | password: 'my super password' | ||
607 | } | ||
608 | |||
609 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 204) | ||
610 | }) | ||
611 | |||
612 | it('Should fail with a non admin user', function (done) { | ||
613 | server.user = { | ||
614 | username: 'user1', | ||
615 | password: 'password' | ||
616 | } | ||
617 | |||
618 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
619 | if (err) throw err | ||
620 | |||
621 | userAccessToken = accessToken | ||
622 | |||
623 | const data = { | ||
624 | username: 'user3', | ||
625 | password: 'my super password' | ||
626 | } | ||
627 | |||
628 | requestsUtils.makePostBodyRequest(server.url, path, userAccessToken, data, done, 403) | ||
629 | }) | ||
630 | }) | ||
631 | }) | ||
632 | |||
633 | describe('When updating a user', function () { | ||
634 | before(function (done) { | ||
635 | usersUtils.getUsersList(server.url, function (err, res) { | ||
636 | if (err) throw err | ||
637 | |||
638 | userId = res.body.data[1].id | ||
639 | rootId = res.body.data[2].id | ||
640 | done() | ||
641 | }) | ||
642 | }) | ||
643 | |||
644 | it('Should fail with a too small password', function (done) { | ||
645 | const data = { | ||
646 | password: 'bla' | ||
647 | } | ||
648 | |||
649 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done) | ||
650 | }) | ||
651 | |||
652 | it('Should fail with a too long password', function (done) { | ||
653 | const data = { | ||
654 | password: 'my super long password which is very very very very very very very very very very very very very very' + | ||
655 | 'very very very very very very very very very very very very very very very veryv very very very very' + | ||
656 | 'very very very very very very very very very very very very very very very very very very very very long' | ||
657 | } | ||
658 | |||
659 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done) | ||
660 | }) | ||
661 | |||
662 | it('Should fail with an non authenticated user', function (done) { | ||
663 | const data = { | ||
664 | password: 'my super password' | ||
665 | } | ||
666 | |||
667 | requestsUtils.makePutBodyRequest(server.url, path + userId, 'super token', data, done, 401) | ||
668 | }) | ||
669 | |||
670 | it('Should succeed with the correct params', function (done) { | ||
671 | const data = { | ||
672 | password: 'my super password' | ||
673 | } | ||
674 | |||
675 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done, 204) | ||
676 | }) | ||
677 | }) | ||
678 | |||
679 | describe('When getting my information', function () { | ||
680 | it('Should fail with a non authenticated user', function (done) { | ||
681 | request(server.url) | ||
682 | .get(path + 'me') | ||
683 | .set('Authorization', 'Bearer faketoken') | ||
684 | .set('Accept', 'application/json') | ||
685 | .expect(401, done) | ||
686 | }) | ||
687 | |||
688 | it('Should success with the correct parameters', function (done) { | ||
689 | request(server.url) | ||
690 | .get(path + 'me') | ||
691 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
692 | .set('Accept', 'application/json') | ||
693 | .expect(200, done) | ||
694 | }) | ||
695 | }) | ||
696 | |||
697 | describe('When removing an user', function () { | ||
698 | it('Should fail with an incorrect id', function (done) { | ||
699 | request(server.url) | ||
700 | .delete(path + 'bla-bla') | ||
701 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
702 | .expect(400, done) | ||
703 | }) | ||
704 | |||
705 | it('Should fail with the root user', function (done) { | ||
706 | request(server.url) | ||
707 | .delete(path + rootId) | ||
708 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
709 | .expect(400, done) | ||
710 | }) | ||
711 | |||
712 | it('Should return 404 with a non existing id', function (done) { | ||
713 | request(server.url) | ||
714 | .delete(path + '579f982228c99c221d8092b8') | ||
715 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
716 | .expect(404, done) | ||
717 | }) | ||
718 | }) | ||
719 | }) | ||
720 | |||
721 | describe('Of the remote videos API', function () { | ||
722 | describe('When making a secure request', function () { | ||
723 | it('Should check a secure request') | ||
724 | }) | ||
725 | |||
726 | describe('When adding a video', function () { | ||
727 | it('Should check when adding a video') | ||
728 | }) | ||
729 | |||
730 | describe('When removing a video', function () { | ||
731 | it('Should check when removing a video') | ||
732 | }) | ||
733 | }) | ||
734 | |||
735 | describe('Of the requests API', function () { | ||
736 | const path = '/api/v1/requests/stats' | ||
737 | |||
738 | it('Should fail with an non authenticated user', function (done) { | ||
739 | request(server.url) | ||
740 | .get(path) | ||
741 | .set('Accept', 'application/json') | ||
742 | .expect(401, done) | ||
743 | }) | ||
744 | |||
745 | it('Should fail with a non admin user', function (done) { | ||
746 | request(server.url) | ||
747 | .get(path) | ||
748 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
749 | .set('Accept', 'application/json') | ||
750 | .expect(403, done) | ||
751 | }) | ||
752 | }) | ||
753 | |||
754 | after(function (done) { | ||
755 | process.kill(-server.app.pid) | ||
756 | |||
757 | // Keep the logs if the test failed | ||
758 | if (this.ok) { | ||
759 | serversUtils.flushTests(done) | ||
760 | } else { | ||
761 | done() | ||
762 | } | ||
763 | }) | ||
764 | }) | ||
diff --git a/server/tests/api/check-params/index.js b/server/tests/api/check-params/index.js new file mode 100644 index 000000000..d0824f08a --- /dev/null +++ b/server/tests/api/check-params/index.js | |||
@@ -0,0 +1,9 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | // Order of the tests we want to execute | ||
4 | require('./pods') | ||
5 | require('./remotes') | ||
6 | require('./users') | ||
7 | require('./requests') | ||
8 | require('./videos') | ||
9 | require('./video-abuses') | ||
diff --git a/server/tests/api/check-params/pods.js b/server/tests/api/check-params/pods.js new file mode 100644 index 000000000..2f85af644 --- /dev/null +++ b/server/tests/api/check-params/pods.js | |||
@@ -0,0 +1,204 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | const series = require('async/series') | ||
5 | |||
6 | const loginUtils = require('../../utils/login') | ||
7 | const requestsUtils = require('../../utils/requests') | ||
8 | const serversUtils = require('../../utils/servers') | ||
9 | const usersUtils = require('../../utils/users') | ||
10 | |||
11 | describe('Test pods API validators', function () { | ||
12 | const path = '/api/v1/pods/' | ||
13 | let server = null | ||
14 | |||
15 | // --------------------------------------------------------------- | ||
16 | |||
17 | before(function (done) { | ||
18 | this.timeout(20000) | ||
19 | |||
20 | series([ | ||
21 | function (next) { | ||
22 | serversUtils.flushTests(next) | ||
23 | }, | ||
24 | function (next) { | ||
25 | serversUtils.runServer(1, function (server1) { | ||
26 | server = server1 | ||
27 | |||
28 | next() | ||
29 | }) | ||
30 | }, | ||
31 | function (next) { | ||
32 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
33 | if (err) throw err | ||
34 | server.accessToken = token | ||
35 | |||
36 | next() | ||
37 | }) | ||
38 | } | ||
39 | ], done) | ||
40 | }) | ||
41 | |||
42 | describe('When making friends', function () { | ||
43 | let userAccessToken = null | ||
44 | |||
45 | before(function (done) { | ||
46 | usersUtils.createUser(server.url, server.accessToken, 'user1', 'password', function () { | ||
47 | server.user = { | ||
48 | username: 'user1', | ||
49 | password: 'password' | ||
50 | } | ||
51 | |||
52 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
53 | if (err) throw err | ||
54 | |||
55 | userAccessToken = accessToken | ||
56 | |||
57 | done() | ||
58 | }) | ||
59 | }) | ||
60 | }) | ||
61 | |||
62 | describe('When making friends', function () { | ||
63 | const body = { | ||
64 | hosts: [ 'localhost:9002' ] | ||
65 | } | ||
66 | |||
67 | it('Should fail without hosts', function (done) { | ||
68 | request(server.url) | ||
69 | .post(path + '/makefriends') | ||
70 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
71 | .set('Accept', 'application/json') | ||
72 | .expect(400, done) | ||
73 | }) | ||
74 | |||
75 | it('Should fail if hosts is not an array', function (done) { | ||
76 | request(server.url) | ||
77 | .post(path + '/makefriends') | ||
78 | .send({ hosts: 'localhost:9002' }) | ||
79 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
80 | .set('Accept', 'application/json') | ||
81 | .expect(400, done) | ||
82 | }) | ||
83 | |||
84 | it('Should fail if the array is not composed by hosts', function (done) { | ||
85 | request(server.url) | ||
86 | .post(path + '/makefriends') | ||
87 | .send({ hosts: [ 'localhost:9002', 'localhost:coucou' ] }) | ||
88 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
89 | .set('Accept', 'application/json') | ||
90 | .expect(400, done) | ||
91 | }) | ||
92 | |||
93 | it('Should fail if the array is composed with http schemes', function (done) { | ||
94 | request(server.url) | ||
95 | .post(path + '/makefriends') | ||
96 | .send({ hosts: [ 'localhost:9002', 'http://localhost:9003' ] }) | ||
97 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
98 | .set('Accept', 'application/json') | ||
99 | .expect(400, done) | ||
100 | }) | ||
101 | |||
102 | it('Should fail if hosts are not unique', function (done) { | ||
103 | request(server.url) | ||
104 | .post(path + '/makefriends') | ||
105 | .send({ urls: [ 'localhost:9002', 'localhost:9002' ] }) | ||
106 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
107 | .set('Accept', 'application/json') | ||
108 | .expect(400, done) | ||
109 | }) | ||
110 | |||
111 | it('Should fail with a invalid token', function (done) { | ||
112 | request(server.url) | ||
113 | .post(path + '/makefriends') | ||
114 | .send(body) | ||
115 | .set('Authorization', 'Bearer faketoken') | ||
116 | .set('Accept', 'application/json') | ||
117 | .expect(401, done) | ||
118 | }) | ||
119 | |||
120 | it('Should fail if the user is not an administrator', function (done) { | ||
121 | request(server.url) | ||
122 | .post(path + '/makefriends') | ||
123 | .send(body) | ||
124 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
125 | .set('Accept', 'application/json') | ||
126 | .expect(403, done) | ||
127 | }) | ||
128 | }) | ||
129 | |||
130 | describe('When quitting friends', function () { | ||
131 | it('Should fail with a invalid token', function (done) { | ||
132 | request(server.url) | ||
133 | .get(path + '/quitfriends') | ||
134 | .query({ start: 'hello' }) | ||
135 | .set('Authorization', 'Bearer faketoken') | ||
136 | .set('Accept', 'application/json') | ||
137 | .expect(401, done) | ||
138 | }) | ||
139 | |||
140 | it('Should fail if the user is not an administrator', function (done) { | ||
141 | request(server.url) | ||
142 | .get(path + '/quitfriends') | ||
143 | .query({ start: 'hello' }) | ||
144 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
145 | .set('Accept', 'application/json') | ||
146 | .expect(403, done) | ||
147 | }) | ||
148 | }) | ||
149 | }) | ||
150 | |||
151 | describe('When adding a pod', function () { | ||
152 | it('Should fail with nothing', function (done) { | ||
153 | const data = {} | ||
154 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
155 | }) | ||
156 | |||
157 | it('Should fail without public key', function (done) { | ||
158 | const data = { | ||
159 | host: 'coucou.com' | ||
160 | } | ||
161 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
162 | }) | ||
163 | |||
164 | it('Should fail without an host', function (done) { | ||
165 | const data = { | ||
166 | publicKey: 'mysuperpublickey' | ||
167 | } | ||
168 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
169 | }) | ||
170 | |||
171 | it('Should fail with an incorrect host', function (done) { | ||
172 | const data = { | ||
173 | host: 'http://coucou.com', | ||
174 | publicKey: 'mysuperpublickey' | ||
175 | } | ||
176 | requestsUtils.makePostBodyRequest(server.url, path, null, data, function () { | ||
177 | data.host = 'http://coucou' | ||
178 | requestsUtils.makePostBodyRequest(server.url, path, null, data, function () { | ||
179 | data.host = 'coucou' | ||
180 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done) | ||
181 | }) | ||
182 | }) | ||
183 | }) | ||
184 | |||
185 | it('Should succeed with the correct parameters', function (done) { | ||
186 | const data = { | ||
187 | host: 'coucou.com', | ||
188 | publicKey: 'mysuperpublickey' | ||
189 | } | ||
190 | requestsUtils.makePostBodyRequest(server.url, path, null, data, done, 200) | ||
191 | }) | ||
192 | }) | ||
193 | |||
194 | after(function (done) { | ||
195 | process.kill(-server.app.pid) | ||
196 | |||
197 | // Keep the logs if the test failed | ||
198 | if (this.ok) { | ||
199 | serversUtils.flushTests(done) | ||
200 | } else { | ||
201 | done() | ||
202 | } | ||
203 | }) | ||
204 | }) | ||
diff --git a/server/tests/api/check-params/remotes.js b/server/tests/api/check-params/remotes.js new file mode 100644 index 000000000..c1ab9fb2b --- /dev/null +++ b/server/tests/api/check-params/remotes.js | |||
@@ -0,0 +1,64 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const series = require('async/series') | ||
4 | |||
5 | const loginUtils = require('../../utils/login') | ||
6 | const serversUtils = require('../../utils/servers') | ||
7 | |||
8 | describe('Test remote videos API validators', function () { | ||
9 | let server = null | ||
10 | |||
11 | // --------------------------------------------------------------- | ||
12 | |||
13 | before(function (done) { | ||
14 | this.timeout(20000) | ||
15 | |||
16 | series([ | ||
17 | function (next) { | ||
18 | serversUtils.flushTests(next) | ||
19 | }, | ||
20 | function (next) { | ||
21 | serversUtils.runServer(1, function (server1) { | ||
22 | server = server1 | ||
23 | |||
24 | next() | ||
25 | }) | ||
26 | }, | ||
27 | function (next) { | ||
28 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
29 | if (err) throw err | ||
30 | server.accessToken = token | ||
31 | |||
32 | next() | ||
33 | }) | ||
34 | } | ||
35 | ], done) | ||
36 | }) | ||
37 | |||
38 | describe('When making a secure request', function () { | ||
39 | it('Should check a secure request') | ||
40 | }) | ||
41 | |||
42 | describe('When adding a video', function () { | ||
43 | it('Should check when adding a video') | ||
44 | }) | ||
45 | |||
46 | describe('When removing a video', function () { | ||
47 | it('Should check when removing a video') | ||
48 | }) | ||
49 | |||
50 | describe('When reporting abuse on a video', function () { | ||
51 | it('Should check when reporting a video abuse') | ||
52 | }) | ||
53 | |||
54 | after(function (done) { | ||
55 | process.kill(-server.app.pid) | ||
56 | |||
57 | // Keep the logs if the test failed | ||
58 | if (this.ok) { | ||
59 | serversUtils.flushTests(done) | ||
60 | } else { | ||
61 | done() | ||
62 | } | ||
63 | }) | ||
64 | }) | ||
diff --git a/server/tests/api/check-params/requests.js b/server/tests/api/check-params/requests.js new file mode 100644 index 000000000..08f58db43 --- /dev/null +++ b/server/tests/api/check-params/requests.js | |||
@@ -0,0 +1,87 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | const series = require('async/series') | ||
5 | |||
6 | const loginUtils = require('../../utils/login') | ||
7 | const usersUtils = require('../../utils/users') | ||
8 | const serversUtils = require('../../utils/servers') | ||
9 | |||
10 | describe('Test requests API validators', function () { | ||
11 | const path = '/api/v1/requests/stats' | ||
12 | let server = null | ||
13 | let userAccessToken = null | ||
14 | |||
15 | // --------------------------------------------------------------- | ||
16 | |||
17 | before(function (done) { | ||
18 | this.timeout(20000) | ||
19 | |||
20 | series([ | ||
21 | function (next) { | ||
22 | serversUtils.flushTests(next) | ||
23 | }, | ||
24 | function (next) { | ||
25 | serversUtils.runServer(1, function (server1) { | ||
26 | server = server1 | ||
27 | |||
28 | next() | ||
29 | }) | ||
30 | }, | ||
31 | function (next) { | ||
32 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
33 | if (err) throw err | ||
34 | server.accessToken = token | ||
35 | |||
36 | next() | ||
37 | }) | ||
38 | }, | ||
39 | function (next) { | ||
40 | const username = 'user' | ||
41 | const password = 'my super password' | ||
42 | |||
43 | usersUtils.createUser(server.url, server.accessToken, username, password, next) | ||
44 | }, | ||
45 | function (next) { | ||
46 | const user = { | ||
47 | username: 'user', | ||
48 | password: 'my super password' | ||
49 | } | ||
50 | |||
51 | loginUtils.getUserAccessToken(server, user, function (err, accessToken) { | ||
52 | if (err) throw err | ||
53 | |||
54 | userAccessToken = accessToken | ||
55 | |||
56 | next() | ||
57 | }) | ||
58 | } | ||
59 | ], done) | ||
60 | }) | ||
61 | |||
62 | it('Should fail with an non authenticated user', function (done) { | ||
63 | request(server.url) | ||
64 | .get(path) | ||
65 | .set('Accept', 'application/json') | ||
66 | .expect(401, done) | ||
67 | }) | ||
68 | |||
69 | it('Should fail with a non admin user', function (done) { | ||
70 | request(server.url) | ||
71 | .get(path) | ||
72 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
73 | .set('Accept', 'application/json') | ||
74 | .expect(403, done) | ||
75 | }) | ||
76 | |||
77 | after(function (done) { | ||
78 | process.kill(-server.app.pid) | ||
79 | |||
80 | // Keep the logs if the test failed | ||
81 | if (this.ok) { | ||
82 | serversUtils.flushTests(done) | ||
83 | } else { | ||
84 | done() | ||
85 | } | ||
86 | }) | ||
87 | }) | ||
diff --git a/server/tests/api/check-params/users.js b/server/tests/api/check-params/users.js new file mode 100644 index 000000000..c1fcf34a4 --- /dev/null +++ b/server/tests/api/check-params/users.js | |||
@@ -0,0 +1,284 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | const series = require('async/series') | ||
5 | |||
6 | const loginUtils = require('../../utils/login') | ||
7 | const requestsUtils = require('../../utils/requests') | ||
8 | const serversUtils = require('../../utils/servers') | ||
9 | const usersUtils = require('../../utils/users') | ||
10 | |||
11 | describe('Test users API validators', function () { | ||
12 | const path = '/api/v1/users/' | ||
13 | let userId = null | ||
14 | let rootId = null | ||
15 | let server = null | ||
16 | let userAccessToken = null | ||
17 | |||
18 | // --------------------------------------------------------------- | ||
19 | |||
20 | before(function (done) { | ||
21 | this.timeout(20000) | ||
22 | |||
23 | series([ | ||
24 | function (next) { | ||
25 | serversUtils.flushTests(next) | ||
26 | }, | ||
27 | function (next) { | ||
28 | serversUtils.runServer(1, function (server1) { | ||
29 | server = server1 | ||
30 | |||
31 | next() | ||
32 | }) | ||
33 | }, | ||
34 | function (next) { | ||
35 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
36 | if (err) throw err | ||
37 | server.accessToken = token | ||
38 | |||
39 | next() | ||
40 | }) | ||
41 | }, | ||
42 | function (next) { | ||
43 | const username = 'user1' | ||
44 | const password = 'my super password' | ||
45 | |||
46 | usersUtils.createUser(server.url, server.accessToken, username, password, next) | ||
47 | }, | ||
48 | function (next) { | ||
49 | const user = { | ||
50 | username: 'user1', | ||
51 | password: 'my super password' | ||
52 | } | ||
53 | |||
54 | loginUtils.getUserAccessToken(server, user, function (err, accessToken) { | ||
55 | if (err) throw err | ||
56 | |||
57 | userAccessToken = accessToken | ||
58 | |||
59 | next() | ||
60 | }) | ||
61 | } | ||
62 | ], done) | ||
63 | }) | ||
64 | |||
65 | describe('When listing users', function () { | ||
66 | it('Should fail with a bad start pagination', function (done) { | ||
67 | request(server.url) | ||
68 | .get(path) | ||
69 | .query({ start: 'hello' }) | ||
70 | .set('Accept', 'application/json') | ||
71 | .expect(400, done) | ||
72 | }) | ||
73 | |||
74 | it('Should fail with a bad count pagination', function (done) { | ||
75 | request(server.url) | ||
76 | .get(path) | ||
77 | .query({ count: 'hello' }) | ||
78 | .set('Accept', 'application/json') | ||
79 | .expect(400, done) | ||
80 | }) | ||
81 | |||
82 | it('Should fail with an incorrect sort', function (done) { | ||
83 | request(server.url) | ||
84 | .get(path) | ||
85 | .query({ sort: 'hello' }) | ||
86 | .set('Accept', 'application/json') | ||
87 | .expect(400, done) | ||
88 | }) | ||
89 | }) | ||
90 | |||
91 | describe('When adding a new user', function () { | ||
92 | it('Should fail with a too small username', function (done) { | ||
93 | const data = { | ||
94 | username: 'ji', | ||
95 | password: 'mysuperpassword' | ||
96 | } | ||
97 | |||
98 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
99 | }) | ||
100 | |||
101 | it('Should fail with a too long username', function (done) { | ||
102 | const data = { | ||
103 | username: 'mysuperusernamewhichisverylong', | ||
104 | password: 'mysuperpassword' | ||
105 | } | ||
106 | |||
107 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
108 | }) | ||
109 | |||
110 | it('Should fail with an incorrect username', function (done) { | ||
111 | const data = { | ||
112 | username: 'my username', | ||
113 | password: 'mysuperpassword' | ||
114 | } | ||
115 | |||
116 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
117 | }) | ||
118 | |||
119 | it('Should fail with a too small password', function (done) { | ||
120 | const data = { | ||
121 | username: 'myusername', | ||
122 | password: 'bla' | ||
123 | } | ||
124 | |||
125 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
126 | }) | ||
127 | |||
128 | it('Should fail with a too long password', function (done) { | ||
129 | const data = { | ||
130 | username: 'myusername', | ||
131 | password: 'my super long password which is very very very very very very very very very very very very very very' + | ||
132 | 'very very very very very very very very very very very very very very very veryv very very very very' + | ||
133 | 'very very very very very very very very very very very very very very very very very very very very long' | ||
134 | } | ||
135 | |||
136 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
137 | }) | ||
138 | |||
139 | it('Should fail with an non authenticated user', function (done) { | ||
140 | const data = { | ||
141 | username: 'myusername', | ||
142 | password: 'my super password' | ||
143 | } | ||
144 | |||
145 | requestsUtils.makePostBodyRequest(server.url, path, 'super token', data, done, 401) | ||
146 | }) | ||
147 | |||
148 | it('Should fail if we add a user with the same username', function (done) { | ||
149 | const data = { | ||
150 | username: 'user1', | ||
151 | password: 'my super password' | ||
152 | } | ||
153 | |||
154 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 409) | ||
155 | }) | ||
156 | |||
157 | it('Should succeed with the correct params', function (done) { | ||
158 | const data = { | ||
159 | username: 'user2', | ||
160 | password: 'my super password' | ||
161 | } | ||
162 | |||
163 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done, 204) | ||
164 | }) | ||
165 | |||
166 | it('Should fail with a non admin user', function (done) { | ||
167 | server.user = { | ||
168 | username: 'user1', | ||
169 | password: 'my super password' | ||
170 | } | ||
171 | |||
172 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
173 | if (err) throw err | ||
174 | |||
175 | userAccessToken = accessToken | ||
176 | |||
177 | const data = { | ||
178 | username: 'user3', | ||
179 | password: 'my super password' | ||
180 | } | ||
181 | |||
182 | requestsUtils.makePostBodyRequest(server.url, path, userAccessToken, data, done, 403) | ||
183 | }) | ||
184 | }) | ||
185 | }) | ||
186 | |||
187 | describe('When updating a user', function () { | ||
188 | before(function (done) { | ||
189 | usersUtils.getUsersList(server.url, function (err, res) { | ||
190 | if (err) throw err | ||
191 | |||
192 | userId = res.body.data[1].id | ||
193 | rootId = res.body.data[2].id | ||
194 | done() | ||
195 | }) | ||
196 | }) | ||
197 | |||
198 | it('Should fail with a too small password', function (done) { | ||
199 | const data = { | ||
200 | password: 'bla' | ||
201 | } | ||
202 | |||
203 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done) | ||
204 | }) | ||
205 | |||
206 | it('Should fail with a too long password', function (done) { | ||
207 | const data = { | ||
208 | password: 'my super long password which is very very very very very very very very very very very very very very' + | ||
209 | 'very very very very very very very very very very very very very very very veryv very very very very' + | ||
210 | 'very very very very very very very very very very very very very very very very very very very very long' | ||
211 | } | ||
212 | |||
213 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done) | ||
214 | }) | ||
215 | |||
216 | it('Should fail with an non authenticated user', function (done) { | ||
217 | const data = { | ||
218 | password: 'my super password' | ||
219 | } | ||
220 | |||
221 | requestsUtils.makePutBodyRequest(server.url, path + userId, 'super token', data, done, 401) | ||
222 | }) | ||
223 | |||
224 | it('Should succeed with the correct params', function (done) { | ||
225 | const data = { | ||
226 | password: 'my super password' | ||
227 | } | ||
228 | |||
229 | requestsUtils.makePutBodyRequest(server.url, path + userId, userAccessToken, data, done, 204) | ||
230 | }) | ||
231 | }) | ||
232 | |||
233 | describe('When getting my information', function () { | ||
234 | it('Should fail with a non authenticated user', function (done) { | ||
235 | request(server.url) | ||
236 | .get(path + 'me') | ||
237 | .set('Authorization', 'Bearer faketoken') | ||
238 | .set('Accept', 'application/json') | ||
239 | .expect(401, done) | ||
240 | }) | ||
241 | |||
242 | it('Should success with the correct parameters', function (done) { | ||
243 | request(server.url) | ||
244 | .get(path + 'me') | ||
245 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
246 | .set('Accept', 'application/json') | ||
247 | .expect(200, done) | ||
248 | }) | ||
249 | }) | ||
250 | |||
251 | describe('When removing an user', function () { | ||
252 | it('Should fail with an incorrect id', function (done) { | ||
253 | request(server.url) | ||
254 | .delete(path + 'bla-bla') | ||
255 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
256 | .expect(400, done) | ||
257 | }) | ||
258 | |||
259 | it('Should fail with the root user', function (done) { | ||
260 | request(server.url) | ||
261 | .delete(path + rootId) | ||
262 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
263 | .expect(400, done) | ||
264 | }) | ||
265 | |||
266 | it('Should return 404 with a non existing id', function (done) { | ||
267 | request(server.url) | ||
268 | .delete(path + '45') | ||
269 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
270 | .expect(404, done) | ||
271 | }) | ||
272 | }) | ||
273 | |||
274 | after(function (done) { | ||
275 | process.kill(-server.app.pid) | ||
276 | |||
277 | // Keep the logs if the test failed | ||
278 | if (this.ok) { | ||
279 | serversUtils.flushTests(done) | ||
280 | } else { | ||
281 | done() | ||
282 | } | ||
283 | }) | ||
284 | }) | ||
diff --git a/server/tests/api/check-params/video-abuses.js b/server/tests/api/check-params/video-abuses.js new file mode 100644 index 000000000..8cb4ccdc1 --- /dev/null +++ b/server/tests/api/check-params/video-abuses.js | |||
@@ -0,0 +1,180 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | const series = require('async/series') | ||
5 | |||
6 | const loginUtils = require('../../utils/login') | ||
7 | const requestsUtils = require('../../utils/requests') | ||
8 | const serversUtils = require('../../utils/servers') | ||
9 | const usersUtils = require('../../utils/users') | ||
10 | const videosUtils = require('../../utils/videos') | ||
11 | |||
12 | describe('Test video abuses API validators', function () { | ||
13 | let server = null | ||
14 | let userAccessToken = null | ||
15 | |||
16 | // --------------------------------------------------------------- | ||
17 | |||
18 | before(function (done) { | ||
19 | this.timeout(20000) | ||
20 | |||
21 | series([ | ||
22 | function (next) { | ||
23 | serversUtils.flushTests(next) | ||
24 | }, | ||
25 | function (next) { | ||
26 | serversUtils.runServer(1, function (server1) { | ||
27 | server = server1 | ||
28 | |||
29 | next() | ||
30 | }) | ||
31 | }, | ||
32 | function (next) { | ||
33 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
34 | if (err) throw err | ||
35 | server.accessToken = token | ||
36 | |||
37 | next() | ||
38 | }) | ||
39 | }, | ||
40 | function (next) { | ||
41 | const username = 'user1' | ||
42 | const password = 'my super password' | ||
43 | |||
44 | usersUtils.createUser(server.url, server.accessToken, username, password, next) | ||
45 | }, | ||
46 | function (next) { | ||
47 | const user = { | ||
48 | username: 'user1', | ||
49 | password: 'my super password' | ||
50 | } | ||
51 | |||
52 | loginUtils.getUserAccessToken(server, user, function (err, accessToken) { | ||
53 | if (err) throw err | ||
54 | |||
55 | userAccessToken = accessToken | ||
56 | |||
57 | next() | ||
58 | }) | ||
59 | }, | ||
60 | // Upload some videos on each pods | ||
61 | function (next) { | ||
62 | const name = 'my super name for pod' | ||
63 | const description = 'my super description for pod' | ||
64 | const tags = [ 'tag' ] | ||
65 | const file = 'video_short2.webm' | ||
66 | videosUtils.uploadVideo(server.url, server.accessToken, name, description, tags, file, next) | ||
67 | }, | ||
68 | function (next) { | ||
69 | videosUtils.getVideosList(server.url, function (err, res) { | ||
70 | if (err) throw err | ||
71 | |||
72 | const videos = res.body.data | ||
73 | server.video = videos[0] | ||
74 | |||
75 | next() | ||
76 | }) | ||
77 | } | ||
78 | ], done) | ||
79 | }) | ||
80 | |||
81 | describe('When listing video abuses', function () { | ||
82 | const path = '/api/v1/videos/abuse' | ||
83 | |||
84 | it('Should fail with a bad start pagination', function (done) { | ||
85 | request(server.url) | ||
86 | .get(path) | ||
87 | .query({ start: 'hello' }) | ||
88 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
89 | .set('Accept', 'application/json') | ||
90 | .expect(400, done) | ||
91 | }) | ||
92 | |||
93 | it('Should fail with a bad count pagination', function (done) { | ||
94 | request(server.url) | ||
95 | .get(path) | ||
96 | .query({ count: 'hello' }) | ||
97 | .set('Accept', 'application/json') | ||
98 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
99 | .expect(400, done) | ||
100 | }) | ||
101 | |||
102 | it('Should fail with an incorrect sort', function (done) { | ||
103 | request(server.url) | ||
104 | .get(path) | ||
105 | .query({ sort: 'hello' }) | ||
106 | .set('Accept', 'application/json') | ||
107 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
108 | .expect(400, done) | ||
109 | }) | ||
110 | |||
111 | it('Should fail with a non authenticated user', function (done) { | ||
112 | request(server.url) | ||
113 | .get(path) | ||
114 | .query({ sort: 'hello' }) | ||
115 | .set('Accept', 'application/json') | ||
116 | .expect(401, done) | ||
117 | }) | ||
118 | |||
119 | it('Should fail with a non admin user', function (done) { | ||
120 | request(server.url) | ||
121 | .get(path) | ||
122 | .query({ sort: 'hello' }) | ||
123 | .set('Accept', 'application/json') | ||
124 | .set('Authorization', 'Bearer ' + userAccessToken) | ||
125 | .expect(403, done) | ||
126 | }) | ||
127 | }) | ||
128 | |||
129 | describe('When reporting a video abuse', function () { | ||
130 | const basePath = '/api/v1/videos/' | ||
131 | |||
132 | it('Should fail with nothing', function (done) { | ||
133 | const path = basePath + server.video + '/abuse' | ||
134 | const data = {} | ||
135 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
136 | }) | ||
137 | |||
138 | it('Should fail with a wrong video', function (done) { | ||
139 | const wrongPath = '/api/v1/videos/blabla/abuse' | ||
140 | const data = {} | ||
141 | requestsUtils.makePostBodyRequest(server.url, wrongPath, server.accessToken, data, done) | ||
142 | }) | ||
143 | |||
144 | it('Should fail with a non authenticated user', function (done) { | ||
145 | const data = {} | ||
146 | const path = basePath + server.video + '/abuse' | ||
147 | requestsUtils.makePostBodyRequest(server.url, path, 'hello', data, done, 401) | ||
148 | }) | ||
149 | |||
150 | it('Should fail with a reason too short', function (done) { | ||
151 | const data = { | ||
152 | reason: 'h' | ||
153 | } | ||
154 | const path = basePath + server.video + '/abuse' | ||
155 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
156 | }) | ||
157 | |||
158 | it('Should fail with a reason too big', function (done) { | ||
159 | const data = { | ||
160 | reason: '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' + | ||
161 | '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' + | ||
162 | '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' + | ||
163 | '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef' | ||
164 | } | ||
165 | const path = basePath + server.video + '/abuse' | ||
166 | requestsUtils.makePostBodyRequest(server.url, path, server.accessToken, data, done) | ||
167 | }) | ||
168 | }) | ||
169 | |||
170 | after(function (done) { | ||
171 | process.kill(-server.app.pid) | ||
172 | |||
173 | // Keep the logs if the test failed | ||
174 | if (this.ok) { | ||
175 | serversUtils.flushTests(done) | ||
176 | } else { | ||
177 | done() | ||
178 | } | ||
179 | }) | ||
180 | }) | ||
diff --git a/server/tests/api/check-params/videos.js b/server/tests/api/check-params/videos.js new file mode 100644 index 000000000..fac903715 --- /dev/null +++ b/server/tests/api/check-params/videos.js | |||
@@ -0,0 +1,460 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const chai = require('chai') | ||
4 | const expect = chai.expect | ||
5 | const pathUtils = require('path') | ||
6 | const request = require('supertest') | ||
7 | const series = require('async/series') | ||
8 | |||
9 | const loginUtils = require('../../utils/login') | ||
10 | const requestsUtils = require('../../utils/requests') | ||
11 | const serversUtils = require('../../utils/servers') | ||
12 | const videosUtils = require('../../utils/videos') | ||
13 | |||
14 | describe('Test videos API validator', function () { | ||
15 | const path = '/api/v1/videos/' | ||
16 | let server = null | ||
17 | |||
18 | // --------------------------------------------------------------- | ||
19 | |||
20 | before(function (done) { | ||
21 | this.timeout(20000) | ||
22 | |||
23 | series([ | ||
24 | function (next) { | ||
25 | serversUtils.flushTests(next) | ||
26 | }, | ||
27 | function (next) { | ||
28 | serversUtils.runServer(1, function (server1) { | ||
29 | server = server1 | ||
30 | |||
31 | next() | ||
32 | }) | ||
33 | }, | ||
34 | function (next) { | ||
35 | loginUtils.loginAndGetAccessToken(server, function (err, token) { | ||
36 | if (err) throw err | ||
37 | server.accessToken = token | ||
38 | |||
39 | next() | ||
40 | }) | ||
41 | } | ||
42 | ], done) | ||
43 | }) | ||
44 | |||
45 | describe('When listing a video', function () { | ||
46 | it('Should fail with a bad start pagination', function (done) { | ||
47 | request(server.url) | ||
48 | .get(path) | ||
49 | .query({ start: 'hello' }) | ||
50 | .set('Accept', 'application/json') | ||
51 | .expect(400, done) | ||
52 | }) | ||
53 | |||
54 | it('Should fail with a bad count pagination', function (done) { | ||
55 | request(server.url) | ||
56 | .get(path) | ||
57 | .query({ count: 'hello' }) | ||
58 | .set('Accept', 'application/json') | ||
59 | .expect(400, done) | ||
60 | }) | ||
61 | |||
62 | it('Should fail with an incorrect sort', function (done) { | ||
63 | request(server.url) | ||
64 | .get(path) | ||
65 | .query({ sort: 'hello' }) | ||
66 | .set('Accept', 'application/json') | ||
67 | .expect(400, done) | ||
68 | }) | ||
69 | }) | ||
70 | |||
71 | describe('When searching a video', function () { | ||
72 | it('Should fail with nothing', function (done) { | ||
73 | request(server.url) | ||
74 | .get(pathUtils.join(path, 'search')) | ||
75 | .set('Accept', 'application/json') | ||
76 | .expect(400, done) | ||
77 | }) | ||
78 | |||
79 | it('Should fail with a bad start pagination', function (done) { | ||
80 | request(server.url) | ||
81 | .get(pathUtils.join(path, 'search', 'test')) | ||
82 | .query({ start: 'hello' }) | ||
83 | .set('Accept', 'application/json') | ||
84 | .expect(400, done) | ||
85 | }) | ||
86 | |||
87 | it('Should fail with a bad count pagination', function (done) { | ||
88 | request(server.url) | ||
89 | .get(pathUtils.join(path, 'search', 'test')) | ||
90 | .query({ count: 'hello' }) | ||
91 | .set('Accept', 'application/json') | ||
92 | .expect(400, done) | ||
93 | }) | ||
94 | |||
95 | it('Should fail with an incorrect sort', function (done) { | ||
96 | request(server.url) | ||
97 | .get(pathUtils.join(path, 'search', 'test')) | ||
98 | .query({ sort: 'hello' }) | ||
99 | .set('Accept', 'application/json') | ||
100 | .expect(400, done) | ||
101 | }) | ||
102 | }) | ||
103 | |||
104 | describe('When adding a video', function () { | ||
105 | it('Should fail with nothing', function (done) { | ||
106 | const data = {} | ||
107 | const attach = {} | ||
108 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
109 | }) | ||
110 | |||
111 | it('Should fail without name', function (done) { | ||
112 | const data = { | ||
113 | description: 'my super description', | ||
114 | tags: [ 'tag1', 'tag2' ] | ||
115 | } | ||
116 | const attach = { | ||
117 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
118 | } | ||
119 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
120 | }) | ||
121 | |||
122 | it('Should fail with a long name', function (done) { | ||
123 | const data = { | ||
124 | name: 'My very very very very very very very very very very very very very very very very long name', | ||
125 | description: 'my super description', | ||
126 | tags: [ 'tag1', 'tag2' ] | ||
127 | } | ||
128 | const attach = { | ||
129 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
130 | } | ||
131 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
132 | }) | ||
133 | |||
134 | it('Should fail without description', function (done) { | ||
135 | const data = { | ||
136 | name: 'my super name', | ||
137 | tags: [ 'tag1', 'tag2' ] | ||
138 | } | ||
139 | const attach = { | ||
140 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
141 | } | ||
142 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
143 | }) | ||
144 | |||
145 | it('Should fail with a long description', function (done) { | ||
146 | const data = { | ||
147 | name: 'my super name', | ||
148 | description: 'my super description which is very very very very very very very very very very very very very very' + | ||
149 | 'very very very very very very very very very very very very very very very very very very very very very' + | ||
150 | 'very very very very very very very very very very very very very very very long', | ||
151 | tags: [ 'tag1', 'tag2' ] | ||
152 | } | ||
153 | const attach = { | ||
154 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
155 | } | ||
156 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
157 | }) | ||
158 | |||
159 | it('Should fail without tags', function (done) { | ||
160 | const data = { | ||
161 | name: 'my super name', | ||
162 | description: 'my super description' | ||
163 | } | ||
164 | const attach = { | ||
165 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
166 | } | ||
167 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
168 | }) | ||
169 | |||
170 | it('Should fail with too many tags', function (done) { | ||
171 | const data = { | ||
172 | name: 'my super name', | ||
173 | description: 'my super description', | ||
174 | tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ] | ||
175 | } | ||
176 | const attach = { | ||
177 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
178 | } | ||
179 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
180 | }) | ||
181 | |||
182 | it('Should fail with not enough tags', function (done) { | ||
183 | const data = { | ||
184 | name: 'my super name', | ||
185 | description: 'my super description', | ||
186 | tags: [ ] | ||
187 | } | ||
188 | const attach = { | ||
189 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
190 | } | ||
191 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
192 | }) | ||
193 | |||
194 | it('Should fail with a tag length too low', function (done) { | ||
195 | const data = { | ||
196 | name: 'my super name', | ||
197 | description: 'my super description', | ||
198 | tags: [ 'tag1', 't' ] | ||
199 | } | ||
200 | const attach = { | ||
201 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
202 | } | ||
203 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
204 | }) | ||
205 | |||
206 | it('Should fail with a tag length too big', function (done) { | ||
207 | const data = { | ||
208 | name: 'my super name', | ||
209 | description: 'my super description', | ||
210 | tags: [ 'mysupertagtoolong', 'tag1' ] | ||
211 | } | ||
212 | const attach = { | ||
213 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
214 | } | ||
215 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
216 | }) | ||
217 | |||
218 | it('Should fail with malformed tags', function (done) { | ||
219 | const data = { | ||
220 | name: 'my super name', | ||
221 | description: 'my super description', | ||
222 | tags: [ 'my tag' ] | ||
223 | } | ||
224 | const attach = { | ||
225 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
226 | } | ||
227 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
228 | }) | ||
229 | |||
230 | it('Should fail without an input file', function (done) { | ||
231 | const data = { | ||
232 | name: 'my super name', | ||
233 | description: 'my super description', | ||
234 | tags: [ 'tag1', 'tag2' ] | ||
235 | } | ||
236 | const attach = {} | ||
237 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
238 | }) | ||
239 | |||
240 | it('Should fail without an incorrect input file', function (done) { | ||
241 | const data = { | ||
242 | name: 'my super name', | ||
243 | description: 'my super description', | ||
244 | tags: [ 'tag1', 'tag2' ] | ||
245 | } | ||
246 | const attach = { | ||
247 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short_fake.webm') | ||
248 | } | ||
249 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
250 | }) | ||
251 | |||
252 | it('Should fail with a too big duration', function (done) { | ||
253 | const data = { | ||
254 | name: 'my super name', | ||
255 | description: 'my super description', | ||
256 | tags: [ 'tag1', 'tag2' ] | ||
257 | } | ||
258 | const attach = { | ||
259 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_too_long.webm') | ||
260 | } | ||
261 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done) | ||
262 | }) | ||
263 | |||
264 | it('Should succeed with the correct parameters', function (done) { | ||
265 | const data = { | ||
266 | name: 'my super name', | ||
267 | description: 'my super description', | ||
268 | tags: [ 'tag1', 'tag2' ] | ||
269 | } | ||
270 | const attach = { | ||
271 | 'videofile': pathUtils.join(__dirname, '..', 'fixtures', 'video_short.webm') | ||
272 | } | ||
273 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () { | ||
274 | attach.videofile = pathUtils.join(__dirname, '..', 'fixtures', 'video_short.mp4') | ||
275 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, function () { | ||
276 | attach.videofile = pathUtils.join(__dirname, '..', 'fixtures', 'video_short.ogv') | ||
277 | requestsUtils.makePostUploadRequest(server.url, path, server.accessToken, data, attach, done, 204) | ||
278 | }, false) | ||
279 | }, false) | ||
280 | }) | ||
281 | }) | ||
282 | |||
283 | describe('When updating a video', function () { | ||
284 | let videoId | ||
285 | |||
286 | before(function (done) { | ||
287 | videosUtils.getVideosList(server.url, function (err, res) { | ||
288 | if (err) throw err | ||
289 | |||
290 | videoId = res.body.data[0].id | ||
291 | |||
292 | return done() | ||
293 | }) | ||
294 | }) | ||
295 | |||
296 | it('Should fail with nothing', function (done) { | ||
297 | const data = {} | ||
298 | requestsUtils.makePutBodyRequest(server.url, path, server.accessToken, data, done) | ||
299 | }) | ||
300 | |||
301 | it('Should fail without a valid uuid', function (done) { | ||
302 | const data = { | ||
303 | description: 'my super description', | ||
304 | tags: [ 'tag1', 'tag2' ] | ||
305 | } | ||
306 | requestsUtils.makePutBodyRequest(server.url, path + 'blabla', server.accessToken, data, done) | ||
307 | }) | ||
308 | |||
309 | it('Should fail with an unknown id', function (done) { | ||
310 | const data = { | ||
311 | description: 'my super description', | ||
312 | tags: [ 'tag1', 'tag2' ] | ||
313 | } | ||
314 | requestsUtils.makePutBodyRequest(server.url, path + '4da6fde3-88f7-4d16-b119-108df5630b06', server.accessToken, data, done, 404) | ||
315 | }) | ||
316 | |||
317 | it('Should fail with a long name', function (done) { | ||
318 | const data = { | ||
319 | name: 'My very very very very very very very very very very very very very very very very long name', | ||
320 | description: 'my super description', | ||
321 | tags: [ 'tag1', 'tag2' ] | ||
322 | } | ||
323 | requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done) | ||
324 | }) | ||
325 | |||
326 | it('Should fail with a long description', function (done) { | ||
327 | const data = { | ||
328 | name: 'my super name', | ||
329 | description: 'my super description which is very very very very very very very very very very very very very very' + | ||
330 | 'very very very very very very very very very very very very very very very very very very very very very' + | ||
331 | 'very very very very very very very very very very very very very very very long', | ||
332 | tags: [ 'tag1', 'tag2' ] | ||
333 | } | ||
334 | requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done) | ||
335 | }) | ||
336 | |||
337 | it('Should fail with too many tags', function (done) { | ||
338 | const data = { | ||
339 | name: 'my super name', | ||
340 | description: 'my super description', | ||
341 | tags: [ 'tag1', 'tag2', 'tag3', 'tag4' ] | ||
342 | } | ||
343 | requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done) | ||
344 | }) | ||
345 | |||
346 | it('Should fail with not enough tags', function (done) { | ||
347 | const data = { | ||
348 | name: 'my super name', | ||
349 | description: 'my super description', | ||
350 | tags: [ ] | ||
351 | } | ||
352 | requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done) | ||
353 | }) | ||
354 | |||
355 | it('Should fail with a tag length too low', function (done) { | ||
356 | const data = { | ||
357 | name: 'my super name', | ||
358 | description: 'my super description', | ||
359 | tags: [ 'tag1', 't' ] | ||
360 | } | ||
361 | requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done) | ||
362 | }) | ||
363 | |||
364 | it('Should fail with a tag length too big', function (done) { | ||
365 | const data = { | ||
366 | name: 'my super name', | ||
367 | description: 'my super description', | ||
368 | tags: [ 'mysupertagtoolong', 'tag1' ] | ||
369 | } | ||
370 | requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done) | ||
371 | }) | ||
372 | |||
373 | it('Should fail with malformed tags', function (done) { | ||
374 | const data = { | ||
375 | name: 'my super name', | ||
376 | description: 'my super description', | ||
377 | tags: [ 'my tag' ] | ||
378 | } | ||
379 | requestsUtils.makePutBodyRequest(server.url, path + videoId, server.accessToken, data, done) | ||
380 | }) | ||
381 | |||
382 | it('Should fail with a video of another user') | ||
383 | |||
384 | it('Should fail with a video of another pod') | ||
385 | }) | ||
386 | |||
387 | describe('When getting a video', function () { | ||
388 | it('Should return the list of the videos with nothing', function (done) { | ||
389 | request(server.url) | ||
390 | .get(path) | ||
391 | .set('Accept', 'application/json') | ||
392 | .expect(200) | ||
393 | .expect('Content-Type', /json/) | ||
394 | .end(function (err, res) { | ||
395 | if (err) throw err | ||
396 | |||
397 | expect(res.body.data).to.be.an('array') | ||
398 | expect(res.body.data.length).to.equal(3) | ||
399 | |||
400 | done() | ||
401 | }) | ||
402 | }) | ||
403 | |||
404 | it('Should fail without a correct uuid', function (done) { | ||
405 | request(server.url) | ||
406 | .get(path + 'coucou') | ||
407 | .set('Accept', 'application/json') | ||
408 | .expect(400, done) | ||
409 | }) | ||
410 | |||
411 | it('Should return 404 with an incorrect video', function (done) { | ||
412 | request(server.url) | ||
413 | .get(path + '4da6fde3-88f7-4d16-b119-108df5630b06') | ||
414 | .set('Accept', 'application/json') | ||
415 | .expect(404, done) | ||
416 | }) | ||
417 | |||
418 | it('Should succeed with the correct parameters') | ||
419 | }) | ||
420 | |||
421 | describe('When removing a video', function () { | ||
422 | it('Should have 404 with nothing', function (done) { | ||
423 | request(server.url) | ||
424 | .delete(path) | ||
425 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
426 | .expect(400, done) | ||
427 | }) | ||
428 | |||
429 | it('Should fail without a correct uuid', function (done) { | ||
430 | request(server.url) | ||
431 | .delete(path + 'hello') | ||
432 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
433 | .expect(400, done) | ||
434 | }) | ||
435 | |||
436 | it('Should fail with a video which does not exist', function (done) { | ||
437 | request(server.url) | ||
438 | .delete(path + '4da6fde3-88f7-4d16-b119-108df5630b06') | ||
439 | .set('Authorization', 'Bearer ' + server.accessToken) | ||
440 | .expect(404, done) | ||
441 | }) | ||
442 | |||
443 | it('Should fail with a video of another user') | ||
444 | |||
445 | it('Should fail with a video of another pod') | ||
446 | |||
447 | it('Should succeed with the correct parameters') | ||
448 | }) | ||
449 | |||
450 | after(function (done) { | ||
451 | process.kill(-server.app.pid) | ||
452 | |||
453 | // Keep the logs if the test failed | ||
454 | if (this.ok) { | ||
455 | serversUtils.flushTests(done) | ||
456 | } else { | ||
457 | done() | ||
458 | } | ||
459 | }) | ||
460 | }) | ||
diff --git a/server/tests/api/friends-advanced.js b/server/tests/api/friends-advanced.js index 0a2d58d82..708138bc9 100644 --- a/server/tests/api/friends-advanced.js +++ b/server/tests/api/friends-advanced.js | |||
@@ -86,7 +86,7 @@ describe('Test advanced friends', function () { | |||
86 | getFriendsList(5, function (err, res) { | 86 | getFriendsList(5, function (err, res) { |
87 | if (err) throw err | 87 | if (err) throw err |
88 | 88 | ||
89 | expect(res.body.length).to.equal(0) | 89 | expect(res.body.data.length).to.equal(0) |
90 | 90 | ||
91 | done() | 91 | done() |
92 | }) | 92 | }) |
@@ -111,7 +111,7 @@ describe('Test advanced friends', function () { | |||
111 | getFriendsList(i, function (err, res) { | 111 | getFriendsList(i, function (err, res) { |
112 | if (err) throw err | 112 | if (err) throw err |
113 | 113 | ||
114 | expect(res.body.length).to.equal(0) | 114 | expect(res.body.data.length).to.equal(0) |
115 | 115 | ||
116 | callback() | 116 | callback() |
117 | }) | 117 | }) |
@@ -140,7 +140,7 @@ describe('Test advanced friends', function () { | |||
140 | getFriendsList(i, function (err, res) { | 140 | getFriendsList(i, function (err, res) { |
141 | if (err) throw err | 141 | if (err) throw err |
142 | 142 | ||
143 | expect(res.body.length).to.equal(3) | 143 | expect(res.body.data.length).to.equal(3) |
144 | 144 | ||
145 | callback() | 145 | callback() |
146 | }) | 146 | }) |
@@ -182,7 +182,7 @@ describe('Test advanced friends', function () { | |||
182 | if (err) throw err | 182 | if (err) throw err |
183 | 183 | ||
184 | // Pod 4 didn't know pod 1 and 2 removed it | 184 | // Pod 4 didn't know pod 1 and 2 removed it |
185 | expect(res.body.length).to.equal(3) | 185 | expect(res.body.data.length).to.equal(3) |
186 | next() | 186 | next() |
187 | }) | 187 | }) |
188 | }, | 188 | }, |
@@ -200,7 +200,7 @@ describe('Test advanced friends', function () { | |||
200 | if (err) throw err | 200 | if (err) throw err |
201 | 201 | ||
202 | // Pod 4 should not be our friend | 202 | // Pod 4 should not be our friend |
203 | const result = res.body | 203 | const result = res.body.data |
204 | expect(result.length).to.equal(3) | 204 | expect(result.length).to.equal(3) |
205 | for (const pod of result) { | 205 | for (const pod of result) { |
206 | expect(pod.host).not.equal(servers[3].host) | 206 | expect(pod.host).not.equal(servers[3].host) |
diff --git a/server/tests/api/friends-basic.js b/server/tests/api/friends-basic.js index a871f9838..6f37ff291 100644 --- a/server/tests/api/friends-basic.js +++ b/server/tests/api/friends-basic.js | |||
@@ -28,7 +28,7 @@ describe('Test basic friends', function () { | |||
28 | podsUtils.getFriendsList(serverToTest.url, function (err, res) { | 28 | podsUtils.getFriendsList(serverToTest.url, function (err, res) { |
29 | if (err) throw err | 29 | if (err) throw err |
30 | 30 | ||
31 | const result = res.body | 31 | const result = res.body.data |
32 | expect(result).to.be.an('array') | 32 | expect(result).to.be.an('array') |
33 | expect(result.length).to.equal(2) | 33 | expect(result.length).to.equal(2) |
34 | 34 | ||
@@ -65,7 +65,7 @@ describe('Test basic friends', function () { | |||
65 | podsUtils.getFriendsList(server.url, function (err, res) { | 65 | podsUtils.getFriendsList(server.url, function (err, res) { |
66 | if (err) throw err | 66 | if (err) throw err |
67 | 67 | ||
68 | const result = res.body | 68 | const result = res.body.data |
69 | expect(result).to.be.an('array') | 69 | expect(result).to.be.an('array') |
70 | expect(result.length).to.equal(0) | 70 | expect(result.length).to.equal(0) |
71 | callback() | 71 | callback() |
@@ -90,14 +90,14 @@ describe('Test basic friends', function () { | |||
90 | podsUtils.getFriendsList(servers[1].url, function (err, res) { | 90 | podsUtils.getFriendsList(servers[1].url, function (err, res) { |
91 | if (err) throw err | 91 | if (err) throw err |
92 | 92 | ||
93 | const result = res.body | 93 | const result = res.body.data |
94 | expect(result).to.be.an('array') | 94 | expect(result).to.be.an('array') |
95 | expect(result.length).to.equal(1) | 95 | expect(result.length).to.equal(1) |
96 | 96 | ||
97 | const pod = result[0] | 97 | const pod = result[0] |
98 | expect(pod.host).to.equal(servers[2].host) | 98 | expect(pod.host).to.equal(servers[2].host) |
99 | expect(pod.score).to.equal(20) | 99 | expect(pod.score).to.equal(20) |
100 | expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true | 100 | expect(miscsUtils.dateIsValid(pod.createdAt)).to.be.true |
101 | 101 | ||
102 | next() | 102 | next() |
103 | }) | 103 | }) |
@@ -107,14 +107,14 @@ describe('Test basic friends', function () { | |||
107 | podsUtils.getFriendsList(servers[2].url, function (err, res) { | 107 | podsUtils.getFriendsList(servers[2].url, function (err, res) { |
108 | if (err) throw err | 108 | if (err) throw err |
109 | 109 | ||
110 | const result = res.body | 110 | const result = res.body.data |
111 | expect(result).to.be.an('array') | 111 | expect(result).to.be.an('array') |
112 | expect(result.length).to.equal(1) | 112 | expect(result.length).to.equal(1) |
113 | 113 | ||
114 | const pod = result[0] | 114 | const pod = result[0] |
115 | expect(pod.host).to.equal(servers[1].host) | 115 | expect(pod.host).to.equal(servers[1].host) |
116 | expect(pod.score).to.equal(20) | 116 | expect(pod.score).to.equal(20) |
117 | expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true | 117 | expect(miscsUtils.dateIsValid(pod.createdAt)).to.be.true |
118 | 118 | ||
119 | next() | 119 | next() |
120 | }) | 120 | }) |
@@ -154,7 +154,7 @@ describe('Test basic friends', function () { | |||
154 | podsUtils.getFriendsList(servers[1].url, function (err, res) { | 154 | podsUtils.getFriendsList(servers[1].url, function (err, res) { |
155 | if (err) throw err | 155 | if (err) throw err |
156 | 156 | ||
157 | const result = res.body | 157 | const result = res.body.data |
158 | expect(result).to.be.an('array') | 158 | expect(result).to.be.an('array') |
159 | expect(result.length).to.equal(0) | 159 | expect(result.length).to.equal(0) |
160 | 160 | ||
@@ -167,7 +167,7 @@ describe('Test basic friends', function () { | |||
167 | podsUtils.getFriendsList(url, function (err, res) { | 167 | podsUtils.getFriendsList(url, function (err, res) { |
168 | if (err) throw err | 168 | if (err) throw err |
169 | 169 | ||
170 | const result = res.body | 170 | const result = res.body.data |
171 | expect(result).to.be.an('array') | 171 | expect(result).to.be.an('array') |
172 | expect(result.length).to.equal(1) | 172 | expect(result.length).to.equal(1) |
173 | expect(result[0].host).not.to.be.equal(servers[1].host) | 173 | expect(result[0].host).not.to.be.equal(servers[1].host) |
diff --git a/server/tests/api/multiple-pods.js b/server/tests/api/multiple-pods.js index be278d7c5..df12ba0e9 100644 --- a/server/tests/api/multiple-pods.js +++ b/server/tests/api/multiple-pods.js | |||
@@ -4,7 +4,8 @@ const chai = require('chai') | |||
4 | const each = require('async/each') | 4 | const each = require('async/each') |
5 | const expect = chai.expect | 5 | const expect = chai.expect |
6 | const series = require('async/series') | 6 | const series = require('async/series') |
7 | const webtorrent = new (require('webtorrent'))() | 7 | const WebTorrent = require('webtorrent') |
8 | const webtorrent = new WebTorrent() | ||
8 | 9 | ||
9 | const loginUtils = require('../utils/login') | 10 | const loginUtils = require('../utils/login') |
10 | const miscsUtils = require('../utils/miscs') | 11 | const miscsUtils = require('../utils/miscs') |
@@ -104,7 +105,8 @@ describe('Test multiple pods', function () { | |||
104 | expect(video.magnetUri).to.exist | 105 | expect(video.magnetUri).to.exist |
105 | expect(video.duration).to.equal(10) | 106 | expect(video.duration).to.equal(10) |
106 | expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) | 107 | expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) |
107 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true | 108 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true |
109 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
108 | expect(video.author).to.equal('root') | 110 | expect(video.author).to.equal('root') |
109 | 111 | ||
110 | if (server.url !== 'http://localhost:9001') { | 112 | if (server.url !== 'http://localhost:9001') { |
@@ -166,7 +168,8 @@ describe('Test multiple pods', function () { | |||
166 | expect(video.magnetUri).to.exist | 168 | expect(video.magnetUri).to.exist |
167 | expect(video.duration).to.equal(5) | 169 | expect(video.duration).to.equal(5) |
168 | expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) | 170 | expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) |
169 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true | 171 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true |
172 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
170 | expect(video.author).to.equal('root') | 173 | expect(video.author).to.equal('root') |
171 | 174 | ||
172 | if (server.url !== 'http://localhost:9002') { | 175 | if (server.url !== 'http://localhost:9002') { |
@@ -246,7 +249,8 @@ describe('Test multiple pods', function () { | |||
246 | expect(video1.duration).to.equal(5) | 249 | expect(video1.duration).to.equal(5) |
247 | expect(video1.tags).to.deep.equal([ 'tag1p3' ]) | 250 | expect(video1.tags).to.deep.equal([ 'tag1p3' ]) |
248 | expect(video1.author).to.equal('root') | 251 | expect(video1.author).to.equal('root') |
249 | expect(miscsUtils.dateIsValid(video1.createdDate)).to.be.true | 252 | expect(miscsUtils.dateIsValid(video1.createdAt)).to.be.true |
253 | expect(miscsUtils.dateIsValid(video1.updatedAt)).to.be.true | ||
250 | 254 | ||
251 | expect(video2.name).to.equal('my super name for pod 3-2') | 255 | expect(video2.name).to.equal('my super name for pod 3-2') |
252 | expect(video2.description).to.equal('my super description for pod 3-2') | 256 | expect(video2.description).to.equal('my super description for pod 3-2') |
@@ -255,7 +259,8 @@ describe('Test multiple pods', function () { | |||
255 | expect(video2.duration).to.equal(5) | 259 | expect(video2.duration).to.equal(5) |
256 | expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) | 260 | expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) |
257 | expect(video2.author).to.equal('root') | 261 | expect(video2.author).to.equal('root') |
258 | expect(miscsUtils.dateIsValid(video2.createdDate)).to.be.true | 262 | expect(miscsUtils.dateIsValid(video2.createdAt)).to.be.true |
263 | expect(miscsUtils.dateIsValid(video2.updatedAt)).to.be.true | ||
259 | 264 | ||
260 | if (server.url !== 'http://localhost:9003') { | 265 | if (server.url !== 'http://localhost:9003') { |
261 | expect(video1.isLocal).to.be.false | 266 | expect(video1.isLocal).to.be.false |
@@ -299,8 +304,8 @@ describe('Test multiple pods', function () { | |||
299 | if (err) throw err | 304 | if (err) throw err |
300 | 305 | ||
301 | const video = res.body.data[0] | 306 | const video = res.body.data[0] |
302 | toRemove.push(res.body.data[2].id) | 307 | toRemove.push(res.body.data[2]) |
303 | toRemove.push(res.body.data[3].id) | 308 | toRemove.push(res.body.data[3]) |
304 | 309 | ||
305 | webtorrent.add(video.magnetUri, function (torrent) { | 310 | webtorrent.add(video.magnetUri, function (torrent) { |
306 | expect(torrent.files).to.exist | 311 | expect(torrent.files).to.exist |
@@ -368,16 +373,68 @@ describe('Test multiple pods', function () { | |||
368 | }) | 373 | }) |
369 | }) | 374 | }) |
370 | }) | 375 | }) |
376 | }) | ||
377 | |||
378 | describe('Should manipulate these videos', function () { | ||
379 | it('Should update the video 3 by asking pod 3', function (done) { | ||
380 | this.timeout(15000) | ||
381 | |||
382 | const name = 'my super video updated' | ||
383 | const description = 'my super description updated' | ||
384 | const tags = [ 'tagup1', 'tagup2' ] | ||
385 | |||
386 | videosUtils.updateVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, name, description, tags, function (err) { | ||
387 | if (err) throw err | ||
388 | |||
389 | setTimeout(done, 11000) | ||
390 | }) | ||
391 | }) | ||
392 | |||
393 | it('Should have the video 3 updated on each pod', function (done) { | ||
394 | this.timeout(200000) | ||
395 | |||
396 | each(servers, function (server, callback) { | ||
397 | // Avoid "duplicate torrent" errors | ||
398 | const webtorrent = new WebTorrent() | ||
399 | |||
400 | videosUtils.getVideosList(server.url, function (err, res) { | ||
401 | if (err) throw err | ||
402 | |||
403 | const videos = res.body.data | ||
404 | const videoUpdated = videos.find(function (video) { | ||
405 | return video.name === 'my super video updated' | ||
406 | }) | ||
407 | |||
408 | expect(!!videoUpdated).to.be.true | ||
409 | expect(videoUpdated.description).to.equal('my super description updated') | ||
410 | expect(videoUpdated.tags).to.deep.equal([ 'tagup1', 'tagup2' ]) | ||
411 | expect(miscsUtils.dateIsValid(videoUpdated.updatedAt, 20000)).to.be.true | ||
412 | |||
413 | videosUtils.testVideoImage(server.url, 'video_short3.webm', videoUpdated.thumbnailPath, function (err, test) { | ||
414 | if (err) throw err | ||
415 | expect(test).to.equal(true) | ||
416 | |||
417 | webtorrent.add(videoUpdated.magnetUri, function (torrent) { | ||
418 | expect(torrent.files).to.exist | ||
419 | expect(torrent.files.length).to.equal(1) | ||
420 | expect(torrent.files[0].path).to.exist.and.to.not.equal('') | ||
421 | |||
422 | callback() | ||
423 | }) | ||
424 | }) | ||
425 | }) | ||
426 | }, done) | ||
427 | }) | ||
371 | 428 | ||
372 | it('Should remove the file 3 and 3-2 by asking pod 3', function (done) { | 429 | it('Should remove the videos 3 and 3-2 by asking pod 3', function (done) { |
373 | this.timeout(15000) | 430 | this.timeout(15000) |
374 | 431 | ||
375 | series([ | 432 | series([ |
376 | function (next) { | 433 | function (next) { |
377 | videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0], next) | 434 | videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[0].id, next) |
378 | }, | 435 | }, |
379 | function (next) { | 436 | function (next) { |
380 | videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1], next) | 437 | videosUtils.removeVideo(servers[2].url, servers[2].accessToken, toRemove[1].id, next) |
381 | }], | 438 | }], |
382 | function (err) { | 439 | function (err) { |
383 | if (err) throw err | 440 | if (err) throw err |
@@ -394,11 +451,11 @@ describe('Test multiple pods', function () { | |||
394 | const videos = res.body.data | 451 | const videos = res.body.data |
395 | expect(videos).to.be.an('array') | 452 | expect(videos).to.be.an('array') |
396 | expect(videos.length).to.equal(2) | 453 | expect(videos.length).to.equal(2) |
397 | expect(videos[0].id).not.to.equal(videos[1].id) | 454 | expect(videos[0].name).not.to.equal(videos[1].name) |
398 | expect(videos[0].id).not.to.equal(toRemove[0]) | 455 | expect(videos[0].name).not.to.equal(toRemove[0].name) |
399 | expect(videos[1].id).not.to.equal(toRemove[0]) | 456 | expect(videos[1].name).not.to.equal(toRemove[0].name) |
400 | expect(videos[0].id).not.to.equal(toRemove[1]) | 457 | expect(videos[0].name).not.to.equal(toRemove[1].name) |
401 | expect(videos[1].id).not.to.equal(toRemove[1]) | 458 | expect(videos[1].name).not.to.equal(toRemove[1].name) |
402 | 459 | ||
403 | callback() | 460 | callback() |
404 | }) | 461 | }) |
diff --git a/server/tests/api/requests.js b/server/tests/api/requests.js index af36f6e34..933ed29b4 100644 --- a/server/tests/api/requests.js +++ b/server/tests/api/requests.js | |||
@@ -69,7 +69,7 @@ describe('Test requests stats', function () { | |||
69 | }) | 69 | }) |
70 | }) | 70 | }) |
71 | 71 | ||
72 | it('Should have the correct request', function (done) { | 72 | it('Should have the correct total request', function (done) { |
73 | this.timeout(15000) | 73 | this.timeout(15000) |
74 | 74 | ||
75 | const server = servers[0] | 75 | const server = servers[0] |
@@ -79,40 +79,16 @@ describe('Test requests stats', function () { | |||
79 | uploadVideo(server, function (err) { | 79 | uploadVideo(server, function (err) { |
80 | if (err) throw err | 80 | if (err) throw err |
81 | 81 | ||
82 | getRequestsStats(server, function (err, res) { | 82 | setTimeout(function () { |
83 | if (err) throw err | 83 | getRequestsStats(server, function (err, res) { |
84 | 84 | if (err) throw err | |
85 | const body = res.body | ||
86 | expect(body.requests).to.have.lengthOf(1) | ||
87 | 85 | ||
88 | const request = body.requests[0] | 86 | const body = res.body |
89 | expect(request.to).to.have.lengthOf(1) | 87 | expect(body.totalRequests).to.equal(1) |
90 | expect(request.request.type).to.equal('add') | ||
91 | 88 | ||
92 | // Wait one cycle | 89 | done() |
93 | setTimeout(done, 10000) | 90 | }) |
94 | }) | 91 | }, 1000) |
95 | }) | ||
96 | }) | ||
97 | |||
98 | it('Should have the correct requests', function (done) { | ||
99 | const server = servers[0] | ||
100 | |||
101 | uploadVideo(server, function (err) { | ||
102 | if (err) throw err | ||
103 | |||
104 | getRequestsStats(server, function (err, res) { | ||
105 | if (err) throw err | ||
106 | |||
107 | const body = res.body | ||
108 | expect(body.requests).to.have.lengthOf(2) | ||
109 | |||
110 | const request = body.requests[1] | ||
111 | expect(request.to).to.have.lengthOf(1) | ||
112 | expect(request.request.type).to.equal('add') | ||
113 | |||
114 | done() | ||
115 | }) | ||
116 | }) | 92 | }) |
117 | }) | 93 | }) |
118 | 94 | ||
diff --git a/server/tests/api/single-pod.js b/server/tests/api/single-pod.js index 65d1a7a65..2db60448f 100644 --- a/server/tests/api/single-pod.js +++ b/server/tests/api/single-pod.js | |||
@@ -82,7 +82,8 @@ describe('Test a single pod', function () { | |||
82 | expect(video.author).to.equal('root') | 82 | expect(video.author).to.equal('root') |
83 | expect(video.isLocal).to.be.true | 83 | expect(video.isLocal).to.be.true |
84 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 84 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
85 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true | 85 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true |
86 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
86 | 87 | ||
87 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 88 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
88 | if (err) throw err | 89 | if (err) throw err |
@@ -116,7 +117,8 @@ describe('Test a single pod', function () { | |||
116 | expect(video.author).to.equal('root') | 117 | expect(video.author).to.equal('root') |
117 | expect(video.isLocal).to.be.true | 118 | expect(video.isLocal).to.be.true |
118 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 119 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
119 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true | 120 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true |
121 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
120 | 122 | ||
121 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 123 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
122 | if (err) throw err | 124 | if (err) throw err |
@@ -142,7 +144,8 @@ describe('Test a single pod', function () { | |||
142 | expect(video.author).to.equal('root') | 144 | expect(video.author).to.equal('root') |
143 | expect(video.isLocal).to.be.true | 145 | expect(video.isLocal).to.be.true |
144 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 146 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
145 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true | 147 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true |
148 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
146 | 149 | ||
147 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 150 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
148 | if (err) throw err | 151 | if (err) throw err |
@@ -153,31 +156,33 @@ describe('Test a single pod', function () { | |||
153 | }) | 156 | }) |
154 | }) | 157 | }) |
155 | 158 | ||
156 | it('Should search the video by podHost', function (done) { | 159 | // Not implemented yet |
157 | videosUtils.searchVideo(server.url, '9001', 'podHost', function (err, res) { | 160 | // it('Should search the video by podHost', function (done) { |
158 | if (err) throw err | 161 | // videosUtils.searchVideo(server.url, '9001', 'host', function (err, res) { |
159 | 162 | // if (err) throw err | |
160 | expect(res.body.total).to.equal(1) | 163 | |
161 | expect(res.body.data).to.be.an('array') | 164 | // expect(res.body.total).to.equal(1) |
162 | expect(res.body.data.length).to.equal(1) | 165 | // expect(res.body.data).to.be.an('array') |
163 | 166 | // expect(res.body.data.length).to.equal(1) | |
164 | const video = res.body.data[0] | 167 | |
165 | expect(video.name).to.equal('my super name') | 168 | // const video = res.body.data[0] |
166 | expect(video.description).to.equal('my super description') | 169 | // expect(video.name).to.equal('my super name') |
167 | expect(video.podHost).to.equal('localhost:9001') | 170 | // expect(video.description).to.equal('my super description') |
168 | expect(video.author).to.equal('root') | 171 | // expect(video.podHost).to.equal('localhost:9001') |
169 | expect(video.isLocal).to.be.true | 172 | // expect(video.author).to.equal('root') |
170 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 173 | // expect(video.isLocal).to.be.true |
171 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true | 174 | // expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
172 | 175 | // expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true | |
173 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 176 | // expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true |
174 | if (err) throw err | 177 | |
175 | expect(test).to.equal(true) | 178 | // videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
176 | 179 | // if (err) throw err | |
177 | done() | 180 | // expect(test).to.equal(true) |
178 | }) | 181 | |
179 | }) | 182 | // done() |
180 | }) | 183 | // }) |
184 | // }) | ||
185 | // }) | ||
181 | 186 | ||
182 | it('Should search the video by tag', function (done) { | 187 | it('Should search the video by tag', function (done) { |
183 | videosUtils.searchVideo(server.url, 'tag1', 'tags', function (err, res) { | 188 | videosUtils.searchVideo(server.url, 'tag1', 'tags', function (err, res) { |
@@ -194,7 +199,8 @@ describe('Test a single pod', function () { | |||
194 | expect(video.author).to.equal('root') | 199 | expect(video.author).to.equal('root') |
195 | expect(video.isLocal).to.be.true | 200 | expect(video.isLocal).to.be.true |
196 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) | 201 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) |
197 | expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true | 202 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true |
203 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
198 | 204 | ||
199 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { | 205 | videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { |
200 | if (err) throw err | 206 | if (err) throw err |
@@ -230,7 +236,7 @@ describe('Test a single pod', function () { | |||
230 | }) | 236 | }) |
231 | 237 | ||
232 | it('Should not find a search by tag', function (done) { | 238 | it('Should not find a search by tag', function (done) { |
233 | videosUtils.searchVideo(server.url, 'tag', 'tags', function (err, res) { | 239 | videosUtils.searchVideo(server.url, 'hello', 'tags', function (err, res) { |
234 | if (err) throw err | 240 | if (err) throw err |
235 | 241 | ||
236 | expect(res.body.total).to.equal(0) | 242 | expect(res.body.total).to.equal(0) |
@@ -332,69 +338,69 @@ describe('Test a single pod', function () { | |||
332 | }) | 338 | }) |
333 | 339 | ||
334 | it('Should list only the two first videos', function (done) { | 340 | it('Should list only the two first videos', function (done) { |
335 | videosUtils.getVideosListPagination(server.url, 0, 2, function (err, res) { | 341 | videosUtils.getVideosListPagination(server.url, 0, 2, 'name', function (err, res) { |
336 | if (err) throw err | 342 | if (err) throw err |
337 | 343 | ||
338 | const videos = res.body.data | 344 | const videos = res.body.data |
339 | expect(res.body.total).to.equal(6) | 345 | expect(res.body.total).to.equal(6) |
340 | expect(videos.length).to.equal(2) | 346 | expect(videos.length).to.equal(2) |
341 | expect(videos[0].name === videosListBase[0].name) | 347 | expect(videos[0].name).to.equal(videosListBase[0].name) |
342 | expect(videos[1].name === videosListBase[1].name) | 348 | expect(videos[1].name).to.equal(videosListBase[1].name) |
343 | 349 | ||
344 | done() | 350 | done() |
345 | }) | 351 | }) |
346 | }) | 352 | }) |
347 | 353 | ||
348 | it('Should list only the next three videos', function (done) { | 354 | it('Should list only the next three videos', function (done) { |
349 | videosUtils.getVideosListPagination(server.url, 2, 3, function (err, res) { | 355 | videosUtils.getVideosListPagination(server.url, 2, 3, 'name', function (err, res) { |
350 | if (err) throw err | 356 | if (err) throw err |
351 | 357 | ||
352 | const videos = res.body.data | 358 | const videos = res.body.data |
353 | expect(res.body.total).to.equal(6) | 359 | expect(res.body.total).to.equal(6) |
354 | expect(videos.length).to.equal(3) | 360 | expect(videos.length).to.equal(3) |
355 | expect(videos[0].name === videosListBase[2].name) | 361 | expect(videos[0].name).to.equal(videosListBase[2].name) |
356 | expect(videos[1].name === videosListBase[3].name) | 362 | expect(videos[1].name).to.equal(videosListBase[3].name) |
357 | expect(videos[2].name === videosListBase[4].name) | 363 | expect(videos[2].name).to.equal(videosListBase[4].name) |
358 | 364 | ||
359 | done() | 365 | done() |
360 | }) | 366 | }) |
361 | }) | 367 | }) |
362 | 368 | ||
363 | it('Should list the last video', function (done) { | 369 | it('Should list the last video', function (done) { |
364 | videosUtils.getVideosListPagination(server.url, 5, 6, function (err, res) { | 370 | videosUtils.getVideosListPagination(server.url, 5, 6, 'name', function (err, res) { |
365 | if (err) throw err | 371 | if (err) throw err |
366 | 372 | ||
367 | const videos = res.body.data | 373 | const videos = res.body.data |
368 | expect(res.body.total).to.equal(6) | 374 | expect(res.body.total).to.equal(6) |
369 | expect(videos.length).to.equal(1) | 375 | expect(videos.length).to.equal(1) |
370 | expect(videos[0].name === videosListBase[5].name) | 376 | expect(videos[0].name).to.equal(videosListBase[5].name) |
371 | 377 | ||
372 | done() | 378 | done() |
373 | }) | 379 | }) |
374 | }) | 380 | }) |
375 | 381 | ||
376 | it('Should search the first video', function (done) { | 382 | it('Should search the first video', function (done) { |
377 | videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, function (err, res) { | 383 | videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 0, 1, 'name', function (err, res) { |
378 | if (err) throw err | 384 | if (err) throw err |
379 | 385 | ||
380 | const videos = res.body.data | 386 | const videos = res.body.data |
381 | expect(res.body.total).to.equal(4) | 387 | expect(res.body.total).to.equal(4) |
382 | expect(videos.length).to.equal(1) | 388 | expect(videos.length).to.equal(1) |
383 | expect(videos[0].name === 'video_short.webm name') | 389 | expect(videos[0].name).to.equal('video_short1.webm name') |
384 | 390 | ||
385 | done() | 391 | done() |
386 | }) | 392 | }) |
387 | }) | 393 | }) |
388 | 394 | ||
389 | it('Should search the last two videos', function (done) { | 395 | it('Should search the last two videos', function (done) { |
390 | videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, function (err, res) { | 396 | videosUtils.searchVideoWithPagination(server.url, 'webm', 'name', 2, 2, 'name', function (err, res) { |
391 | if (err) throw err | 397 | if (err) throw err |
392 | 398 | ||
393 | const videos = res.body.data | 399 | const videos = res.body.data |
394 | expect(res.body.total).to.equal(4) | 400 | expect(res.body.total).to.equal(4) |
395 | expect(videos.length).to.equal(2) | 401 | expect(videos.length).to.equal(2) |
396 | expect(videos[0].name === 'video_short2.webm name') | 402 | expect(videos[0].name).to.equal('video_short3.webm name') |
397 | expect(videos[1].name === 'video_short3.webm name') | 403 | expect(videos[1].name).to.equal('video_short.webm name') |
398 | 404 | ||
399 | done() | 405 | done() |
400 | }) | 406 | }) |
@@ -424,29 +430,30 @@ describe('Test a single pod', function () { | |||
424 | }) | 430 | }) |
425 | }) | 431 | }) |
426 | 432 | ||
427 | it('Should search all the 9001 port videos', function (done) { | 433 | // Not implemented yet |
428 | videosUtils.searchVideoWithPagination(server.url, '9001', 'podHost', 0, 15, function (err, res) { | 434 | // it('Should search all the 9001 port videos', function (done) { |
429 | if (err) throw err | 435 | // videosUtils.searchVideoWithPagination(server.url, '9001', 'host', 0, 15, function (err, res) { |
436 | // if (err) throw err | ||
430 | 437 | ||
431 | const videos = res.body.data | 438 | // const videos = res.body.data |
432 | expect(res.body.total).to.equal(6) | 439 | // expect(res.body.total).to.equal(6) |
433 | expect(videos.length).to.equal(6) | 440 | // expect(videos.length).to.equal(6) |
434 | 441 | ||
435 | done() | 442 | // done() |
436 | }) | 443 | // }) |
437 | }) | 444 | // }) |
438 | 445 | ||
439 | it('Should search all the localhost videos', function (done) { | 446 | // it('Should search all the localhost videos', function (done) { |
440 | videosUtils.searchVideoWithPagination(server.url, 'localhost', 'podHost', 0, 15, function (err, res) { | 447 | // videosUtils.searchVideoWithPagination(server.url, 'localhost', 'host', 0, 15, function (err, res) { |
441 | if (err) throw err | 448 | // if (err) throw err |
442 | 449 | ||
443 | const videos = res.body.data | 450 | // const videos = res.body.data |
444 | expect(res.body.total).to.equal(6) | 451 | // expect(res.body.total).to.equal(6) |
445 | expect(videos.length).to.equal(6) | 452 | // expect(videos.length).to.equal(6) |
446 | 453 | ||
447 | done() | 454 | // done() |
448 | }) | 455 | // }) |
449 | }) | 456 | // }) |
450 | 457 | ||
451 | it('Should search the good magnetUri video', function (done) { | 458 | it('Should search the good magnetUri video', function (done) { |
452 | const video = videosListBase[0] | 459 | const video = videosListBase[0] |
@@ -469,12 +476,12 @@ describe('Test a single pod', function () { | |||
469 | const videos = res.body.data | 476 | const videos = res.body.data |
470 | expect(res.body.total).to.equal(6) | 477 | expect(res.body.total).to.equal(6) |
471 | expect(videos.length).to.equal(6) | 478 | expect(videos.length).to.equal(6) |
472 | expect(videos[5].name === 'video_short.mp4 name') | 479 | expect(videos[0].name).to.equal('video_short.webm name') |
473 | expect(videos[4].name === 'video_short.ogv name') | 480 | expect(videos[1].name).to.equal('video_short.ogv name') |
474 | expect(videos[3].name === 'video_short.webm name') | 481 | expect(videos[2].name).to.equal('video_short.mp4 name') |
475 | expect(videos[2].name === 'video_short1.webm name') | 482 | expect(videos[3].name).to.equal('video_short3.webm name') |
476 | expect(videos[1].name === 'video_short2.webm name') | 483 | expect(videos[4].name).to.equal('video_short2.webm name') |
477 | expect(videos[0].name === 'video_short3.webm name') | 484 | expect(videos[5].name).to.equal('video_short1.webm name') |
478 | 485 | ||
479 | done() | 486 | done() |
480 | }) | 487 | }) |
@@ -488,15 +495,107 @@ describe('Test a single pod', function () { | |||
488 | expect(res.body.total).to.equal(4) | 495 | expect(res.body.total).to.equal(4) |
489 | expect(videos.length).to.equal(4) | 496 | expect(videos.length).to.equal(4) |
490 | 497 | ||
491 | expect(videos[0].name === 'video_short.webm name') | 498 | expect(videos[0].name).to.equal('video_short1.webm name') |
492 | expect(videos[1].name === 'video_short1.webm name') | 499 | expect(videos[1].name).to.equal('video_short2.webm name') |
493 | expect(videos[2].name === 'video_short2.webm name') | 500 | expect(videos[2].name).to.equal('video_short3.webm name') |
494 | expect(videos[3].name === 'video_short3.webm name') | 501 | expect(videos[3].name).to.equal('video_short.webm name') |
502 | |||
503 | videoId = videos[2].id | ||
495 | 504 | ||
496 | done() | 505 | done() |
497 | }) | 506 | }) |
498 | }) | 507 | }) |
499 | 508 | ||
509 | it('Should update a video', function (done) { | ||
510 | const name = 'my super video updated' | ||
511 | const description = 'my super description updated' | ||
512 | const tags = [ 'tagup1', 'tagup2' ] | ||
513 | |||
514 | videosUtils.updateVideo(server.url, server.accessToken, videoId, name, description, tags, done) | ||
515 | }) | ||
516 | |||
517 | it('Should have the video updated', function (done) { | ||
518 | this.timeout(60000) | ||
519 | |||
520 | videosUtils.getVideo(server.url, videoId, function (err, res) { | ||
521 | if (err) throw err | ||
522 | |||
523 | const video = res.body | ||
524 | |||
525 | expect(video.name).to.equal('my super video updated') | ||
526 | expect(video.description).to.equal('my super description updated') | ||
527 | expect(video.podHost).to.equal('localhost:9001') | ||
528 | expect(video.author).to.equal('root') | ||
529 | expect(video.isLocal).to.be.true | ||
530 | expect(video.tags).to.deep.equal([ 'tagup1', 'tagup2' ]) | ||
531 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true | ||
532 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
533 | |||
534 | videosUtils.testVideoImage(server.url, 'video_short3.webm', video.thumbnailPath, function (err, test) { | ||
535 | if (err) throw err | ||
536 | expect(test).to.equal(true) | ||
537 | |||
538 | webtorrent.add(video.magnetUri, function (torrent) { | ||
539 | expect(torrent.files).to.exist | ||
540 | expect(torrent.files.length).to.equal(1) | ||
541 | expect(torrent.files[0].path).to.exist.and.to.not.equal('') | ||
542 | |||
543 | done() | ||
544 | }) | ||
545 | }) | ||
546 | }) | ||
547 | }) | ||
548 | |||
549 | it('Should update only the tags of a video', function (done) { | ||
550 | const tags = [ 'tag1', 'tag2', 'supertag' ] | ||
551 | |||
552 | videosUtils.updateVideo(server.url, server.accessToken, videoId, null, null, tags, function (err) { | ||
553 | if (err) throw err | ||
554 | |||
555 | videosUtils.getVideo(server.url, videoId, function (err, res) { | ||
556 | if (err) throw err | ||
557 | |||
558 | const video = res.body | ||
559 | |||
560 | expect(video.name).to.equal('my super video updated') | ||
561 | expect(video.description).to.equal('my super description updated') | ||
562 | expect(video.podHost).to.equal('localhost:9001') | ||
563 | expect(video.author).to.equal('root') | ||
564 | expect(video.isLocal).to.be.true | ||
565 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'supertag' ]) | ||
566 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true | ||
567 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
568 | |||
569 | done() | ||
570 | }) | ||
571 | }) | ||
572 | }) | ||
573 | |||
574 | it('Should update only the description of a video', function (done) { | ||
575 | const description = 'hello everybody' | ||
576 | |||
577 | videosUtils.updateVideo(server.url, server.accessToken, videoId, null, description, null, function (err) { | ||
578 | if (err) throw err | ||
579 | |||
580 | videosUtils.getVideo(server.url, videoId, function (err, res) { | ||
581 | if (err) throw err | ||
582 | |||
583 | const video = res.body | ||
584 | |||
585 | expect(video.name).to.equal('my super video updated') | ||
586 | expect(video.description).to.equal('hello everybody') | ||
587 | expect(video.podHost).to.equal('localhost:9001') | ||
588 | expect(video.author).to.equal('root') | ||
589 | expect(video.isLocal).to.be.true | ||
590 | expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'supertag' ]) | ||
591 | expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true | ||
592 | expect(miscsUtils.dateIsValid(video.updatedAt)).to.be.true | ||
593 | |||
594 | done() | ||
595 | }) | ||
596 | }) | ||
597 | }) | ||
598 | |||
500 | after(function (done) { | 599 | after(function (done) { |
501 | process.kill(-server.app.pid) | 600 | process.kill(-server.app.pid) |
502 | 601 | ||
diff --git a/server/tests/api/users.js b/server/tests/api/users.js index 94267f104..e6d937eb0 100644 --- a/server/tests/api/users.js +++ b/server/tests/api/users.js | |||
@@ -261,8 +261,8 @@ describe('Test users', function () { | |||
261 | }) | 261 | }) |
262 | }) | 262 | }) |
263 | 263 | ||
264 | it('Should list only the second user by createdDate desc', function (done) { | 264 | it('Should list only the second user by createdAt desc', function (done) { |
265 | usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdDate', function (err, res) { | 265 | usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdAt', function (err, res) { |
266 | if (err) throw err | 266 | if (err) throw err |
267 | 267 | ||
268 | const result = res.body | 268 | const result = res.body |
@@ -279,8 +279,8 @@ describe('Test users', function () { | |||
279 | }) | 279 | }) |
280 | }) | 280 | }) |
281 | 281 | ||
282 | it('Should list all the users by createdDate asc', function (done) { | 282 | it('Should list all the users by createdAt asc', function (done) { |
283 | usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdDate', function (err, res) { | 283 | usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdAt', function (err, res) { |
284 | if (err) throw err | 284 | if (err) throw err |
285 | 285 | ||
286 | const result = res.body | 286 | const result = res.body |
diff --git a/server/tests/api/video-abuse.js b/server/tests/api/video-abuse.js new file mode 100644 index 000000000..58db17c42 --- /dev/null +++ b/server/tests/api/video-abuse.js | |||
@@ -0,0 +1,191 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const chai = require('chai') | ||
4 | const each = require('async/each') | ||
5 | const expect = chai.expect | ||
6 | const series = require('async/series') | ||
7 | |||
8 | const loginUtils = require('../utils/login') | ||
9 | const podsUtils = require('../utils/pods') | ||
10 | const serversUtils = require('../utils/servers') | ||
11 | const videosUtils = require('../utils/videos') | ||
12 | const videoAbusesUtils = require('../utils/video-abuses') | ||
13 | |||
14 | describe('Test video abuses', function () { | ||
15 | let servers = [] | ||
16 | |||
17 | before(function (done) { | ||
18 | this.timeout(30000) | ||
19 | |||
20 | series([ | ||
21 | // Run servers | ||
22 | function (next) { | ||
23 | serversUtils.flushAndRunMultipleServers(2, function (serversRun) { | ||
24 | servers = serversRun | ||
25 | next() | ||
26 | }) | ||
27 | }, | ||
28 | // Get the access tokens | ||
29 | function (next) { | ||
30 | each(servers, function (server, callbackEach) { | ||
31 | loginUtils.loginAndGetAccessToken(server, function (err, accessToken) { | ||
32 | if (err) return callbackEach(err) | ||
33 | |||
34 | server.accessToken = accessToken | ||
35 | callbackEach() | ||
36 | }) | ||
37 | }, next) | ||
38 | }, | ||
39 | // Pod 1 make friends too | ||
40 | function (next) { | ||
41 | const server = servers[0] | ||
42 | podsUtils.makeFriends(server.url, server.accessToken, next) | ||
43 | }, | ||
44 | // Upload some videos on each pods | ||
45 | function (next) { | ||
46 | const name = 'my super name for pod 1' | ||
47 | const description = 'my super description for pod 1' | ||
48 | const tags = [ 'tag' ] | ||
49 | const file = 'video_short2.webm' | ||
50 | videosUtils.uploadVideo(servers[0].url, servers[0].accessToken, name, description, tags, file, next) | ||
51 | }, | ||
52 | function (next) { | ||
53 | const name = 'my super name for pod 2' | ||
54 | const description = 'my super description for pod 2' | ||
55 | const tags = [ 'tag' ] | ||
56 | const file = 'video_short2.webm' | ||
57 | videosUtils.uploadVideo(servers[1].url, servers[1].accessToken, name, description, tags, file, next) | ||
58 | }, | ||
59 | // Wait videos propagation | ||
60 | function (next) { | ||
61 | setTimeout(next, 11000) | ||
62 | }, | ||
63 | function (next) { | ||
64 | videosUtils.getVideosList(servers[0].url, function (err, res) { | ||
65 | if (err) throw err | ||
66 | |||
67 | const videos = res.body.data | ||
68 | |||
69 | expect(videos.length).to.equal(2) | ||
70 | |||
71 | servers[0].video = videos.find(function (video) { return video.name === 'my super name for pod 1' }) | ||
72 | servers[1].video = videos.find(function (video) { return video.name === 'my super name for pod 2' }) | ||
73 | |||
74 | next() | ||
75 | }) | ||
76 | } | ||
77 | ], done) | ||
78 | }) | ||
79 | |||
80 | it('Should not have video abuses', function (done) { | ||
81 | videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) { | ||
82 | if (err) throw err | ||
83 | |||
84 | expect(res.body.total).to.equal(0) | ||
85 | expect(res.body.data).to.be.an('array') | ||
86 | expect(res.body.data.length).to.equal(0) | ||
87 | |||
88 | done() | ||
89 | }) | ||
90 | }) | ||
91 | |||
92 | it('Should report abuse on a local video', function (done) { | ||
93 | this.timeout(15000) | ||
94 | |||
95 | const reason = 'my super bad reason' | ||
96 | videoAbusesUtils.reportVideoAbuse(servers[0].url, servers[0].accessToken, servers[0].video.id, reason, function (err) { | ||
97 | if (err) throw err | ||
98 | |||
99 | // We wait requests propagation, even if the pod 1 is not supposed to make a request to pod 2 | ||
100 | setTimeout(done, 11000) | ||
101 | }) | ||
102 | }) | ||
103 | |||
104 | it('Should have 1 video abuses on pod 1 and 0 on pod 2', function (done) { | ||
105 | videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) { | ||
106 | if (err) throw err | ||
107 | |||
108 | expect(res.body.total).to.equal(1) | ||
109 | expect(res.body.data).to.be.an('array') | ||
110 | expect(res.body.data.length).to.equal(1) | ||
111 | |||
112 | const abuse = res.body.data[0] | ||
113 | expect(abuse.reason).to.equal('my super bad reason') | ||
114 | expect(abuse.reporterUsername).to.equal('root') | ||
115 | expect(abuse.reporterPodHost).to.equal('localhost:9001') | ||
116 | expect(abuse.videoId).to.equal(servers[0].video.id) | ||
117 | |||
118 | videoAbusesUtils.getVideoAbusesList(servers[1].url, servers[1].accessToken, function (err, res) { | ||
119 | if (err) throw err | ||
120 | |||
121 | expect(res.body.total).to.equal(0) | ||
122 | expect(res.body.data).to.be.an('array') | ||
123 | expect(res.body.data.length).to.equal(0) | ||
124 | |||
125 | done() | ||
126 | }) | ||
127 | }) | ||
128 | }) | ||
129 | |||
130 | it('Should report abuse on a remote video', function (done) { | ||
131 | this.timeout(15000) | ||
132 | |||
133 | const reason = 'my super bad reason 2' | ||
134 | videoAbusesUtils.reportVideoAbuse(servers[0].url, servers[0].accessToken, servers[1].video.id, reason, function (err) { | ||
135 | if (err) throw err | ||
136 | |||
137 | // We wait requests propagation | ||
138 | setTimeout(done, 11000) | ||
139 | }) | ||
140 | }) | ||
141 | |||
142 | it('Should have 2 video abuse on pod 1 and 1 on pod 2', function (done) { | ||
143 | videoAbusesUtils.getVideoAbusesList(servers[0].url, servers[0].accessToken, function (err, res) { | ||
144 | if (err) throw err | ||
145 | |||
146 | expect(res.body.total).to.equal(2) | ||
147 | expect(res.body.data).to.be.an('array') | ||
148 | expect(res.body.data.length).to.equal(2) | ||
149 | |||
150 | let abuse = res.body.data[0] | ||
151 | expect(abuse.reason).to.equal('my super bad reason') | ||
152 | expect(abuse.reporterUsername).to.equal('root') | ||
153 | expect(abuse.reporterPodHost).to.equal('localhost:9001') | ||
154 | expect(abuse.videoId).to.equal(servers[0].video.id) | ||
155 | |||
156 | abuse = res.body.data[1] | ||
157 | expect(abuse.reason).to.equal('my super bad reason 2') | ||
158 | expect(abuse.reporterUsername).to.equal('root') | ||
159 | expect(abuse.reporterPodHost).to.equal('localhost:9001') | ||
160 | expect(abuse.videoId).to.equal(servers[1].video.id) | ||
161 | |||
162 | videoAbusesUtils.getVideoAbusesList(servers[1].url, servers[1].accessToken, function (err, res) { | ||
163 | if (err) throw err | ||
164 | |||
165 | expect(res.body.total).to.equal(1) | ||
166 | expect(res.body.data).to.be.an('array') | ||
167 | expect(res.body.data.length).to.equal(1) | ||
168 | |||
169 | let abuse = res.body.data[0] | ||
170 | expect(abuse.reason).to.equal('my super bad reason 2') | ||
171 | expect(abuse.reporterUsername).to.equal('root') | ||
172 | expect(abuse.reporterPodHost).to.equal('localhost:9001') | ||
173 | |||
174 | done() | ||
175 | }) | ||
176 | }) | ||
177 | }) | ||
178 | |||
179 | after(function (done) { | ||
180 | servers.forEach(function (server) { | ||
181 | process.kill(-server.app.pid) | ||
182 | }) | ||
183 | |||
184 | // Keep the logs if the test failed | ||
185 | if (this.ok) { | ||
186 | serversUtils.flushTests(done) | ||
187 | } else { | ||
188 | done() | ||
189 | } | ||
190 | }) | ||
191 | }) | ||
diff --git a/server/tests/real-world/real-world.js b/server/tests/real-world/real-world.js index dba1970c5..12ab06d6d 100644 --- a/server/tests/real-world/real-world.js +++ b/server/tests/real-world/real-world.js | |||
@@ -2,6 +2,7 @@ | |||
2 | 2 | ||
3 | const each = require('async/each') | 3 | const each = require('async/each') |
4 | const isEqual = require('lodash/isEqual') | 4 | const isEqual = require('lodash/isEqual') |
5 | const differenceWith = require('lodash/differenceWith') | ||
5 | const program = require('commander') | 6 | const program = require('commander') |
6 | const series = require('async/series') | 7 | const series = require('async/series') |
7 | 8 | ||
@@ -16,32 +17,41 @@ const videosUtils = require('../utils/videos') | |||
16 | program | 17 | program |
17 | .option('-c, --create [weight]', 'Weight for creating videos') | 18 | .option('-c, --create [weight]', 'Weight for creating videos') |
18 | .option('-r, --remove [weight]', 'Weight for removing videos') | 19 | .option('-r, --remove [weight]', 'Weight for removing videos') |
20 | .option('-u, --update [weight]', 'Weight for updating videos') | ||
19 | .option('-p, --pods [n]', 'Number of pods to run (3 or 6)', /^3|6$/, 3) | 21 | .option('-p, --pods [n]', 'Number of pods to run (3 or 6)', /^3|6$/, 3) |
20 | .option('-a, --action [interval]', 'Interval in ms for an action') | 22 | .option('-a, --action [interval]', 'Interval in ms for an action') |
21 | .option('-i, --integrity [interval]', 'Interval in ms for an integrity check') | 23 | .option('-i, --integrity [interval]', 'Interval in ms for an integrity check') |
22 | .option('-f, --flush', 'Flush datas on exit') | 24 | .option('-f, --flush', 'Flush datas on exit') |
25 | .option('-d, --difference', 'Display difference if integrity is not okay') | ||
23 | .parse(process.argv) | 26 | .parse(process.argv) |
24 | 27 | ||
25 | const createWeight = parseInt(program.create) || 5 | 28 | const createWeight = program.create !== undefined ? parseInt(program.create) : 5 |
26 | const removeWeight = parseInt(program.remove) || 4 | 29 | const removeWeight = program.remove !== undefined ? parseInt(program.remove) : 4 |
30 | const updateWeight = program.update !== undefined ? parseInt(program.update) : 4 | ||
27 | const flushAtExit = program.flush || false | 31 | const flushAtExit = program.flush || false |
28 | const actionInterval = parseInt(program.action) || 500 | 32 | const actionInterval = program.action !== undefined ? parseInt(program.action) : 500 |
29 | let integrityInterval = parseInt(program.integrity) || 60000 | 33 | const integrityInterval = program.integrity !== undefined ? parseInt(program.integrity) : 60000 |
34 | const displayDiffOnFail = program.integrity || false | ||
30 | 35 | ||
31 | const numberOfPods = 6 | 36 | const numberOfPods = 6 |
32 | // Wait requests between pods | ||
33 | const requestsMaxPerInterval = constants.INTERVAL / actionInterval | ||
34 | const intervalsToMakeAllRequests = Math.ceil(requestsMaxPerInterval / constants.REQUESTS_LIMIT) | ||
35 | const waitForBeforeIntegrityCheck = (intervalsToMakeAllRequests * constants.INTERVAL) + 1000 | ||
36 | 37 | ||
37 | integrityInterval += waitForBeforeIntegrityCheck | 38 | // Wait requests between pods |
39 | const baseRequestInterval = integrityInterval < constants.REQUESTS_INTERVAL ? constants.REQUESTS_INTERVAL : integrityInterval | ||
40 | const requestsMaxPerInterval = baseRequestInterval / actionInterval | ||
41 | const intervalsToMakeAllRequests = Math.ceil(requestsMaxPerInterval / constants.REQUESTS_LIMIT_PER_POD) | ||
42 | const waitForBeforeIntegrityCheck = (intervalsToMakeAllRequests * constants.REQUESTS_INTERVAL) - integrityInterval + 1000 | ||
38 | 43 | ||
39 | console.log('Create weight: %d, remove weight: %d.', createWeight, removeWeight) | 44 | console.log('Create weight: %d, update weight: %d, remove weight: %d.', createWeight, updateWeight, removeWeight) |
40 | if (flushAtExit) { | 45 | if (flushAtExit) { |
41 | console.log('Program will flush data on exit.') | 46 | console.log('Program will flush data on exit.') |
42 | } else { | 47 | } else { |
43 | console.log('Program will not flush data on exit.') | 48 | console.log('Program will not flush data on exit.') |
44 | } | 49 | } |
50 | if (displayDiffOnFail) { | ||
51 | console.log('Program will display diff on failure.') | ||
52 | } else { | ||
53 | console.log('Program will not display diff on failure') | ||
54 | } | ||
45 | console.log('Interval in ms for each action: %d.', actionInterval) | 55 | console.log('Interval in ms for each action: %d.', actionInterval) |
46 | console.log('Interval in ms for each integrity check: %d.', integrityInterval) | 56 | console.log('Interval in ms for each integrity check: %d.', integrityInterval) |
47 | console.log('Will wait %d ms before an integrity check.', waitForBeforeIntegrityCheck) | 57 | console.log('Will wait %d ms before an integrity check.', waitForBeforeIntegrityCheck) |
@@ -63,16 +73,20 @@ runServers(numberOfPods, function (err, servers) { | |||
63 | setInterval(function () { | 73 | setInterval(function () { |
64 | if (checking === true) return | 74 | if (checking === true) return |
65 | 75 | ||
66 | const rand = getRandomInt(0, createWeight + removeWeight) | 76 | const rand = getRandomInt(0, createWeight + updateWeight + removeWeight) |
67 | 77 | ||
68 | if (rand < createWeight) { | 78 | if (rand < createWeight) { |
69 | upload(servers, getRandomNumServer(servers)) | 79 | upload(servers, getRandomNumServer(servers)) |
80 | } else if (rand < createWeight + updateWeight) { | ||
81 | update(servers, getRandomNumServer(servers)) | ||
70 | } else { | 82 | } else { |
71 | remove(servers, getRandomNumServer(servers)) | 83 | remove(servers, getRandomNumServer(servers)) |
72 | } | 84 | } |
73 | }, actionInterval) | 85 | }, actionInterval) |
74 | 86 | ||
75 | setInterval(function () { | 87 | setInterval(function () { |
88 | if (checking === true) return | ||
89 | |||
76 | console.log('Checking integrity...') | 90 | console.log('Checking integrity...') |
77 | checking = true | 91 | checking = true |
78 | 92 | ||
@@ -160,9 +174,9 @@ function exitServers (servers, callback) { | |||
160 | function upload (servers, numServer, callback) { | 174 | function upload (servers, numServer, callback) { |
161 | if (!callback) callback = function () {} | 175 | if (!callback) callback = function () {} |
162 | 176 | ||
163 | const name = 'my super name for pod 1' | 177 | const name = Date.now() + ' name' |
164 | const description = 'my super description for pod 1' | 178 | const description = Date.now() + ' description' |
165 | const tags = [ 'tag1p1', 'tag2p1' ] | 179 | const tags = [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ] |
166 | const file = 'video_short1.webm' | 180 | const file = 'video_short1.webm' |
167 | 181 | ||
168 | console.log('Upload video to server ' + numServer) | 182 | console.log('Upload video to server ' + numServer) |
@@ -170,6 +184,26 @@ function upload (servers, numServer, callback) { | |||
170 | videosUtils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback) | 184 | videosUtils.uploadVideo(servers[numServer].url, servers[numServer].accessToken, name, description, tags, file, callback) |
171 | } | 185 | } |
172 | 186 | ||
187 | function update (servers, numServer, callback) { | ||
188 | if (!callback) callback = function () {} | ||
189 | |||
190 | videosUtils.getVideosList(servers[numServer].url, function (err, res) { | ||
191 | if (err) throw err | ||
192 | |||
193 | const videos = res.body.data.filter(function (video) { return video.isLocal }) | ||
194 | if (videos.length === 0) return callback() | ||
195 | |||
196 | const toUpdate = videos[getRandomInt(0, videos.length)].id | ||
197 | const name = Date.now() + ' name' | ||
198 | const description = Date.now() + ' description' | ||
199 | const tags = [ Date.now().toString().substring(0, 5) + 't1', Date.now().toString().substring(0, 5) + 't2' ] | ||
200 | |||
201 | console.log('Updating video of server ' + numServer) | ||
202 | |||
203 | videosUtils.updateVideo(servers[numServer].url, servers[numServer].accessToken, toUpdate, name, description, tags, callback) | ||
204 | }) | ||
205 | } | ||
206 | |||
173 | function remove (servers, numServer, callback) { | 207 | function remove (servers, numServer, callback) { |
174 | if (!callback) callback = function () {} | 208 | if (!callback) callback = function () {} |
175 | 209 | ||
@@ -196,6 +230,7 @@ function checkIntegrity (servers, callback) { | |||
196 | delete serverVideo.id | 230 | delete serverVideo.id |
197 | delete serverVideo.isLocal | 231 | delete serverVideo.isLocal |
198 | delete serverVideo.thumbnailPath | 232 | delete serverVideo.thumbnailPath |
233 | delete serverVideo.updatedAt | ||
199 | } | 234 | } |
200 | 235 | ||
201 | videos.push(serverVideos) | 236 | videos.push(serverVideos) |
@@ -205,6 +240,11 @@ function checkIntegrity (servers, callback) { | |||
205 | for (const video of videos) { | 240 | for (const video of videos) { |
206 | if (!isEqual(video, videos[0])) { | 241 | if (!isEqual(video, videos[0])) { |
207 | console.error('Integrity not ok!') | 242 | console.error('Integrity not ok!') |
243 | |||
244 | if (displayDiffOnFail) { | ||
245 | console.log(differenceWith(videos[0], video, isEqual)) | ||
246 | } | ||
247 | |||
208 | process.exit(-1) | 248 | process.exit(-1) |
209 | } | 249 | } |
210 | } | 250 | } |
diff --git a/server/tests/utils/login.js b/server/tests/utils/login.js index 465564e14..c984c0baf 100644 --- a/server/tests/utils/login.js +++ b/server/tests/utils/login.js | |||
@@ -4,7 +4,8 @@ const request = require('supertest') | |||
4 | 4 | ||
5 | const loginUtils = { | 5 | const loginUtils = { |
6 | login, | 6 | login, |
7 | loginAndGetAccessToken | 7 | loginAndGetAccessToken, |
8 | getUserAccessToken | ||
8 | } | 9 | } |
9 | 10 | ||
10 | // ---------------------- Export functions -------------------- | 11 | // ---------------------- Export functions -------------------- |
@@ -43,6 +44,14 @@ function loginAndGetAccessToken (server, callback) { | |||
43 | }) | 44 | }) |
44 | } | 45 | } |
45 | 46 | ||
47 | function getUserAccessToken (server, user, callback) { | ||
48 | login(server.url, server.client, user, 200, function (err, res) { | ||
49 | if (err) return callback(err) | ||
50 | |||
51 | return callback(null, res.body.access_token) | ||
52 | }) | ||
53 | } | ||
54 | |||
46 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
47 | 56 | ||
48 | module.exports = loginUtils | 57 | module.exports = loginUtils |
diff --git a/server/tests/utils/miscs.js b/server/tests/utils/miscs.js index 4ceff65df..c4b661496 100644 --- a/server/tests/utils/miscs.js +++ b/server/tests/utils/miscs.js | |||
@@ -6,12 +6,14 @@ const miscsUtils = { | |||
6 | 6 | ||
7 | // ---------------------- Export functions -------------------- | 7 | // ---------------------- Export functions -------------------- |
8 | 8 | ||
9 | function dateIsValid (dateString) { | 9 | function dateIsValid (dateString, interval) { |
10 | const dateToCheck = new Date(dateString) | 10 | const dateToCheck = new Date(dateString) |
11 | const now = new Date() | 11 | const now = new Date() |
12 | 12 | ||
13 | // Check if the interval is more than 2 minutes | 13 | // Check if the interval is more than 2 minutes |
14 | if (now - dateToCheck > 120000) return false | 14 | if (!interval) interval = 120000 |
15 | |||
16 | if (now - dateToCheck > interval) return false | ||
15 | 17 | ||
16 | return true | 18 | return true |
17 | } | 19 | } |
diff --git a/server/tests/utils/servers.js b/server/tests/utils/servers.js index 01c9a2f39..1946ef49a 100644 --- a/server/tests/utils/servers.js +++ b/server/tests/utils/servers.js | |||
@@ -34,7 +34,7 @@ function flushAndRunMultipleServers (totalServers, serversRun) { | |||
34 | runServer(j, function (app, url) { | 34 | runServer(j, function (app, url) { |
35 | anotherServerDone(j, app, url) | 35 | anotherServerDone(j, app, url) |
36 | }) | 36 | }) |
37 | }, 1000 * j) | 37 | }, 1000 * (j - 1)) |
38 | } | 38 | } |
39 | }) | 39 | }) |
40 | } | 40 | } |
@@ -60,12 +60,12 @@ function runServer (number, callback) { | |||
60 | 60 | ||
61 | // These actions are async so we need to be sure that they have both been done | 61 | // These actions are async so we need to be sure that they have both been done |
62 | const serverRunString = { | 62 | const serverRunString = { |
63 | 'Connected to mongodb': false, | 63 | 'Database is ready': false, |
64 | 'Server listening on port': false | 64 | 'Server listening on port': false |
65 | } | 65 | } |
66 | 66 | ||
67 | const regexps = { | 67 | const regexps = { |
68 | client_id: 'Client id: ([a-f0-9]+)', | 68 | client_id: 'Client id: (.+)', |
69 | client_secret: 'Client secret: (.+)', | 69 | client_secret: 'Client secret: (.+)', |
70 | user_username: 'Username: (.+)', | 70 | user_username: 'Username: (.+)', |
71 | user_password: 'User password: (.+)' | 71 | user_password: 'User password: (.+)' |
@@ -103,7 +103,7 @@ function runServer (number, callback) { | |||
103 | if (serverRunString[key] === false) dontContinue = true | 103 | if (serverRunString[key] === false) dontContinue = true |
104 | } | 104 | } |
105 | 105 | ||
106 | // If no, there is maybe one thing not already initialized (mongodb...) | 106 | // If no, there is maybe one thing not already initialized (client/user credentials generation...) |
107 | if (dontContinue === true) return | 107 | if (dontContinue === true) return |
108 | 108 | ||
109 | server.app.stdout.removeListener('data', onStdout) | 109 | server.app.stdout.removeListener('data', onStdout) |
diff --git a/server/tests/utils/video-abuses.js b/server/tests/utils/video-abuses.js new file mode 100644 index 000000000..596c824b3 --- /dev/null +++ b/server/tests/utils/video-abuses.js | |||
@@ -0,0 +1,73 @@ | |||
1 | 'use strict' | ||
2 | |||
3 | const request = require('supertest') | ||
4 | |||
5 | const videosUtils = { | ||
6 | getVideoAbusesList, | ||
7 | getVideoAbusesListPagination, | ||
8 | getVideoAbusesListSort, | ||
9 | reportVideoAbuse | ||
10 | } | ||
11 | |||
12 | // ---------------------- Export functions -------------------- | ||
13 | |||
14 | function reportVideoAbuse (url, token, videoId, reason, specialStatus, end) { | ||
15 | if (!end) { | ||
16 | end = specialStatus | ||
17 | specialStatus = 204 | ||
18 | } | ||
19 | |||
20 | const path = '/api/v1/videos/' + videoId + '/abuse' | ||
21 | |||
22 | request(url) | ||
23 | .post(path) | ||
24 | .set('Accept', 'application/json') | ||
25 | .set('Authorization', 'Bearer ' + token) | ||
26 | .send({ reason }) | ||
27 | .expect(specialStatus) | ||
28 | .end(end) | ||
29 | } | ||
30 | |||
31 | function getVideoAbusesList (url, token, end) { | ||
32 | const path = '/api/v1/videos/abuse' | ||
33 | |||
34 | request(url) | ||
35 | .get(path) | ||
36 | .query({ sort: 'createdAt' }) | ||
37 | .set('Accept', 'application/json') | ||
38 | .set('Authorization', 'Bearer ' + token) | ||
39 | .expect(200) | ||
40 | .expect('Content-Type', /json/) | ||
41 | .end(end) | ||
42 | } | ||
43 | |||
44 | function getVideoAbusesListPagination (url, token, start, count, end) { | ||
45 | const path = '/api/v1/videos/abuse' | ||
46 | |||
47 | request(url) | ||
48 | .get(path) | ||
49 | .query({ start: start }) | ||
50 | .query({ count: count }) | ||
51 | .set('Accept', 'application/json') | ||
52 | .set('Authorization', 'Bearer ' + token) | ||
53 | .expect(200) | ||
54 | .expect('Content-Type', /json/) | ||
55 | .end(end) | ||
56 | } | ||
57 | |||
58 | function getVideoAbusesListSort (url, token, sort, end) { | ||
59 | const path = '/api/v1/videos/abuse' | ||
60 | |||
61 | request(url) | ||
62 | .get(path) | ||
63 | .query({ sort: sort }) | ||
64 | .set('Accept', 'application/json') | ||
65 | .set('Authorization', 'Bearer ' + token) | ||
66 | .expect(200) | ||
67 | .expect('Content-Type', /json/) | ||
68 | .end(end) | ||
69 | } | ||
70 | |||
71 | // --------------------------------------------------------------------------- | ||
72 | |||
73 | module.exports = videosUtils | ||
diff --git a/server/tests/utils/videos.js b/server/tests/utils/videos.js index 536093db1..f94368437 100644 --- a/server/tests/utils/videos.js +++ b/server/tests/utils/videos.js | |||
@@ -15,7 +15,8 @@ const videosUtils = { | |||
15 | searchVideoWithPagination, | 15 | searchVideoWithPagination, |
16 | searchVideoWithSort, | 16 | searchVideoWithSort, |
17 | testVideoImage, | 17 | testVideoImage, |
18 | uploadVideo | 18 | uploadVideo, |
19 | updateVideo | ||
19 | } | 20 | } |
20 | 21 | ||
21 | // ---------------------- Export functions -------------------- | 22 | // ---------------------- Export functions -------------------- |
@@ -25,7 +26,7 @@ function getAllVideosListBy (url, end) { | |||
25 | 26 | ||
26 | request(url) | 27 | request(url) |
27 | .get(path) | 28 | .get(path) |
28 | .query({ sort: 'createdDate' }) | 29 | .query({ sort: 'createdAt' }) |
29 | .query({ start: 0 }) | 30 | .query({ start: 0 }) |
30 | .query({ count: 10000 }) | 31 | .query({ count: 10000 }) |
31 | .set('Accept', 'application/json') | 32 | .set('Accept', 'application/json') |
@@ -57,17 +58,25 @@ function getVideosList (url, end) { | |||
57 | .end(end) | 58 | .end(end) |
58 | } | 59 | } |
59 | 60 | ||
60 | function getVideosListPagination (url, start, count, end) { | 61 | function getVideosListPagination (url, start, count, sort, end) { |
62 | if (!end) { | ||
63 | end = sort | ||
64 | sort = null | ||
65 | } | ||
66 | |||
61 | const path = '/api/v1/videos' | 67 | const path = '/api/v1/videos' |
62 | 68 | ||
63 | request(url) | 69 | const req = request(url) |
64 | .get(path) | 70 | .get(path) |
65 | .query({ start: start }) | 71 | .query({ start: start }) |
66 | .query({ count: count }) | 72 | .query({ count: count }) |
67 | .set('Accept', 'application/json') | 73 | |
68 | .expect(200) | 74 | if (sort) req.query({ sort }) |
69 | .expect('Content-Type', /json/) | 75 | |
70 | .end(end) | 76 | req.set('Accept', 'application/json') |
77 | .expect(200) | ||
78 | .expect('Content-Type', /json/) | ||
79 | .end(end) | ||
71 | } | 80 | } |
72 | 81 | ||
73 | function getVideosListSort (url, sort, end) { | 82 | function getVideosListSort (url, sort, end) { |
@@ -115,18 +124,26 @@ function searchVideo (url, search, field, end) { | |||
115 | .end(end) | 124 | .end(end) |
116 | } | 125 | } |
117 | 126 | ||
118 | function searchVideoWithPagination (url, search, field, start, count, end) { | 127 | function searchVideoWithPagination (url, search, field, start, count, sort, end) { |
128 | if (!end) { | ||
129 | end = sort | ||
130 | sort = null | ||
131 | } | ||
132 | |||
119 | const path = '/api/v1/videos' | 133 | const path = '/api/v1/videos' |
120 | 134 | ||
121 | request(url) | 135 | const req = request(url) |
122 | .get(path + '/search/' + search) | 136 | .get(path + '/search/' + search) |
123 | .query({ start: start }) | 137 | .query({ start: start }) |
124 | .query({ count: count }) | 138 | .query({ count: count }) |
125 | .query({ field: field }) | 139 | .query({ field: field }) |
126 | .set('Accept', 'application/json') | 140 | |
127 | .expect(200) | 141 | if (sort) req.query({ sort }) |
128 | .expect('Content-Type', /json/) | 142 | |
129 | .end(end) | 143 | req.set('Accept', 'application/json') |
144 | .expect(200) | ||
145 | .expect('Content-Type', /json/) | ||
146 | .end(end) | ||
130 | } | 147 | } |
131 | 148 | ||
132 | function searchVideoWithSort (url, search, sort, end) { | 149 | function searchVideoWithSort (url, search, sort, end) { |
@@ -194,6 +211,31 @@ function uploadVideo (url, accessToken, name, description, tags, fixture, specia | |||
194 | .end(end) | 211 | .end(end) |
195 | } | 212 | } |
196 | 213 | ||
214 | function updateVideo (url, accessToken, id, name, description, tags, specialStatus, end) { | ||
215 | if (!end) { | ||
216 | end = specialStatus | ||
217 | specialStatus = 204 | ||
218 | } | ||
219 | |||
220 | const path = '/api/v1/videos/' + id | ||
221 | |||
222 | const req = request(url) | ||
223 | .put(path) | ||
224 | .set('Accept', 'application/json') | ||
225 | .set('Authorization', 'Bearer ' + accessToken) | ||
226 | |||
227 | if (name) req.field('name', name) | ||
228 | if (description) req.field('description', description) | ||
229 | |||
230 | if (tags) { | ||
231 | for (let i = 0; i < tags.length; i++) { | ||
232 | req.field('tags[' + i + ']', tags[i]) | ||
233 | } | ||
234 | } | ||
235 | |||
236 | req.expect(specialStatus).end(end) | ||
237 | } | ||
238 | |||
197 | // --------------------------------------------------------------------------- | 239 | // --------------------------------------------------------------------------- |
198 | 240 | ||
199 | module.exports = videosUtils | 241 | module.exports = videosUtils |