aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/v1/index.js17
-rw-r--r--server/controllers/api/v1/pods.js93
-rw-r--r--server/controllers/api/v1/remoteVideos.js53
-rw-r--r--server/controllers/api/v1/videos.js144
-rw-r--r--server/controllers/index.js11
-rw-r--r--server/controllers/views.js27
-rw-r--r--server/helpers/customValidators.js32
-rw-r--r--server/helpers/logger.js40
-rw-r--r--server/helpers/peertubeCrypto.js147
-rw-r--r--server/helpers/requests.js109
-rw-r--r--server/helpers/utils.js16
-rw-r--r--server/initializers/checker.js46
-rw-r--r--server/initializers/constants.js42
-rw-r--r--server/initializers/database.js29
-rw-r--r--server/lib/friends.js228
-rw-r--r--server/lib/poolRequests.js221
-rw-r--r--server/lib/videos.js50
-rw-r--r--server/lib/webtorrent.js157
-rw-r--r--server/lib/webtorrentProcess.js92
-rw-r--r--server/middlewares/cache.js23
-rw-r--r--server/middlewares/index.js15
-rw-r--r--server/middlewares/reqValidators/index.js15
-rw-r--r--server/middlewares/reqValidators/pods.js39
-rw-r--r--server/middlewares/reqValidators/remote.js43
-rw-r--r--server/middlewares/reqValidators/utils.js25
-rw-r--r--server/middlewares/reqValidators/videos.js74
-rw-r--r--server/middlewares/secure.js49
-rw-r--r--server/models/pods.js88
-rw-r--r--server/models/poolRequests.js55
-rw-r--r--server/models/videos.js234
-rw-r--r--server/tests/api/checkParams.js300
-rw-r--r--server/tests/api/fixtures/video_short.mp4bin0 -> 38783 bytes
-rw-r--r--server/tests/api/fixtures/video_short.ogvbin0 -> 140849 bytes
-rw-r--r--server/tests/api/fixtures/video_short.webmbin0 -> 218910 bytes
-rw-r--r--server/tests/api/fixtures/video_short1.webmbin0 -> 572456 bytes
-rw-r--r--server/tests/api/fixtures/video_short2.webmbin0 -> 942961 bytes
-rw-r--r--server/tests/api/fixtures/video_short3.webmbin0 -> 292677 bytes
-rw-r--r--server/tests/api/fixtures/video_short_fake.webm1
-rw-r--r--server/tests/api/friendsAdvanced.js250
-rw-r--r--server/tests/api/friendsBasic.js185
-rw-r--r--server/tests/api/index.js8
-rw-r--r--server/tests/api/multiplePods.js328
-rw-r--r--server/tests/api/singlePod.js146
-rw-r--r--server/tests/api/utils.js185
-rw-r--r--server/tests/index.js6
45 files changed, 3623 insertions, 0 deletions
diff --git a/server/controllers/api/v1/index.js b/server/controllers/api/v1/index.js
new file mode 100644
index 000000000..07a68ed9d
--- /dev/null
+++ b/server/controllers/api/v1/index.js
@@ -0,0 +1,17 @@
1'use strict'
2
3var express = require('express')
4
5var router = express.Router()
6
7var podsController = require('./pods')
8var remoteVideosController = require('./remoteVideos')
9var videosController = require('./videos')
10
11router.use('/pods', podsController)
12router.use('/remotevideos', remoteVideosController)
13router.use('/videos', videosController)
14
15// ---------------------------------------------------------------------------
16
17module.exports = router
diff --git a/server/controllers/api/v1/pods.js b/server/controllers/api/v1/pods.js
new file mode 100644
index 000000000..c93a86ee8
--- /dev/null
+++ b/server/controllers/api/v1/pods.js
@@ -0,0 +1,93 @@
1'use strict'
2
3var express = require('express')
4var fs = require('fs')
5
6var logger = require('../../../helpers/logger')
7var friends = require('../../../lib/friends')
8var middleware = require('../../../middlewares')
9var cacheMiddleware = middleware.cache
10var peertubeCrypto = require('../../../helpers/peertubeCrypto')
11var Pods = require('../../../models/pods')
12var reqValidator = middleware.reqValidators.pods
13var secureMiddleware = middleware.secure
14var secureRequest = middleware.reqValidators.remote.secureRequest
15var Videos = require('../../../models/videos')
16
17var router = express.Router()
18
19router.get('/', cacheMiddleware.cache(false), listPods)
20router.post('/', reqValidator.podsAdd, cacheMiddleware.cache(false), addPods)
21router.get('/makefriends', reqValidator.makeFriends, cacheMiddleware.cache(false), makeFriends)
22router.get('/quitfriends', cacheMiddleware.cache(false), quitFriends)
23// Post because this is a secured request
24router.post('/remove', secureRequest, secureMiddleware.decryptBody, removePods)
25
26// ---------------------------------------------------------------------------
27
28module.exports = router
29
30// ---------------------------------------------------------------------------
31
32function addPods (req, res, next) {
33 var informations = req.body.data
34 Pods.add(informations, function (err) {
35 if (err) return next(err)
36
37 Videos.addRemotes(informations.videos)
38
39 fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
40 if (err) {
41 logger.error('Cannot read cert file.')
42 return next(err)
43 }
44
45 Videos.listOwned(function (err, videos_list) {
46 if (err) {
47 logger.error('Cannot get the list of owned videos.')
48 return next(err)
49 }
50
51 res.json({ cert: cert, videos: videos_list })
52 })
53 })
54 })
55}
56
57function listPods (req, res, next) {
58 Pods.list(function (err, pods_list) {
59 if (err) return next(err)
60
61 res.json(pods_list)
62 })
63}
64
65function makeFriends (req, res, next) {
66 friends.makeFriends(function (err) {
67 if (err) return next(err)
68
69 res.sendStatus(204)
70 })
71}
72
73function removePods (req, res, next) {
74 var url = req.body.signature.url
75 Pods.remove(url, function (err) {
76 if (err) return next(err)
77
78 Videos.removeAllRemotesOf(url, function (err) {
79 if (err) logger.error('Cannot remove all remote videos of %s.', url)
80 else logger.info('%s pod removed.', url)
81
82 res.sendStatus(204)
83 })
84 })
85}
86
87function quitFriends (req, res, next) {
88 friends.quitFriends(function (err) {
89 if (err) return next(err)
90
91 res.sendStatus(204)
92 })
93}
diff --git a/server/controllers/api/v1/remoteVideos.js b/server/controllers/api/v1/remoteVideos.js
new file mode 100644
index 000000000..475a874cf
--- /dev/null
+++ b/server/controllers/api/v1/remoteVideos.js
@@ -0,0 +1,53 @@
1'use strict'
2
3var express = require('express')
4var pluck = require('lodash-node/compat/collection/pluck')
5
6var middleware = require('../../../middlewares')
7var secureMiddleware = middleware.secure
8var cacheMiddleware = middleware.cache
9var reqValidator = middleware.reqValidators.remote
10var videos = require('../../../models/videos')
11
12var router = express.Router()
13
14router.post('/add',
15 reqValidator.secureRequest,
16 secureMiddleware.decryptBody,
17 reqValidator.remoteVideosAdd,
18 cacheMiddleware.cache(false),
19 addRemoteVideos
20)
21
22router.post('/remove',
23 reqValidator.secureRequest,
24 secureMiddleware.decryptBody,
25 reqValidator.remoteVideosRemove,
26 cacheMiddleware.cache(false),
27 removeRemoteVideo
28)
29
30// ---------------------------------------------------------------------------
31
32module.exports = router
33
34// ---------------------------------------------------------------------------
35
36function addRemoteVideos (req, res, next) {
37 videos.addRemotes(req.body.data, function (err, videos) {
38 if (err) return next(err)
39
40 res.json(videos)
41 })
42}
43
44function removeRemoteVideo (req, res, next) {
45 var url = req.body.signature.url
46 var magnetUris = pluck(req.body.data, 'magnetUri')
47
48 videos.removeRemotesOfByMagnetUris(url, magnetUris, function (err) {
49 if (err) return next(err)
50
51 res.sendStatus(204)
52 })
53}
diff --git a/server/controllers/api/v1/videos.js b/server/controllers/api/v1/videos.js
new file mode 100644
index 000000000..620711925
--- /dev/null
+++ b/server/controllers/api/v1/videos.js
@@ -0,0 +1,144 @@
1'use strict'
2
3var config = require('config')
4var crypto = require('crypto')
5var express = require('express')
6var multer = require('multer')
7
8var logger = require('../../../helpers/logger')
9var friends = require('../../../lib/friends')
10var middleware = require('../../../middlewares')
11var cacheMiddleware = middleware.cache
12var reqValidator = middleware.reqValidators.videos
13var Videos = require('../../../models/videos') // model
14var videos = require('../../../lib/videos')
15var webtorrent = require('../../../lib/webtorrent')
16
17var router = express.Router()
18var uploads = config.get('storage.uploads')
19
20// multer configuration
21var storage = multer.diskStorage({
22 destination: function (req, file, cb) {
23 cb(null, uploads)
24 },
25
26 filename: function (req, file, cb) {
27 var extension = ''
28 if (file.mimetype === 'video/webm') extension = 'webm'
29 else if (file.mimetype === 'video/mp4') extension = 'mp4'
30 else if (file.mimetype === 'video/ogg') extension = 'ogv'
31 crypto.pseudoRandomBytes(16, function (err, raw) {
32 var fieldname = err ? undefined : raw.toString('hex')
33 cb(null, fieldname + '.' + extension)
34 })
35 }
36})
37
38var reqFiles = multer({ storage: storage }).fields([{ name: 'input_video', maxCount: 1 }])
39
40router.get('/', cacheMiddleware.cache(false), listVideos)
41router.post('/', reqFiles, reqValidator.videosAdd, cacheMiddleware.cache(false), addVideo)
42router.get('/:id', reqValidator.videosGet, cacheMiddleware.cache(false), getVideos)
43router.delete('/:id', reqValidator.videosRemove, cacheMiddleware.cache(false), removeVideo)
44router.get('/search/:name', reqValidator.videosSearch, cacheMiddleware.cache(false), searchVideos)
45
46// ---------------------------------------------------------------------------
47
48module.exports = router
49
50// ---------------------------------------------------------------------------
51
52function addVideo (req, res, next) {
53 var video_file = req.files.input_video[0]
54 var video_infos = req.body
55
56 videos.seed(video_file.path, function (err, torrent) {
57 if (err) {
58 logger.error('Cannot seed this video.')
59 return next(err)
60 }
61
62 var video_data = {
63 name: video_infos.name,
64 namePath: video_file.filename,
65 description: video_infos.description,
66 magnetUri: torrent.magnetURI
67 }
68
69 Videos.add(video_data, function (err) {
70 if (err) {
71 // TODO unseed the video
72 logger.error('Cannot insert this video in the database.')
73 return next(err)
74 }
75
76 // Now we'll add the video's meta data to our friends
77 friends.addVideoToFriends(video_data)
78
79 // TODO : include Location of the new video
80 res.sendStatus(201)
81 })
82 })
83}
84
85function getVideos (req, res, next) {
86 Videos.get(req.params.id, function (err, video) {
87 if (err) return next(err)
88
89 if (video === null) {
90 return res.sendStatus(404)
91 }
92
93 res.json(video)
94 })
95}
96
97function listVideos (req, res, next) {
98 Videos.list(function (err, videos_list) {
99 if (err) return next(err)
100
101 res.json(videos_list)
102 })
103}
104
105function removeVideo (req, res, next) {
106 var video_id = req.params.id
107 Videos.get(video_id, function (err, video) {
108 if (err) return next(err)
109
110 removeTorrent(video.magnetUri, function () {
111 Videos.removeOwned(req.params.id, function (err) {
112 if (err) return next(err)
113
114 var params = {
115 name: video.name,
116 magnetUri: video.magnetUri
117 }
118
119 friends.removeVideoToFriends(params)
120 res.sendStatus(204)
121 })
122 })
123 })
124}
125
126function searchVideos (req, res, next) {
127 Videos.search(req.params.name, function (err, videos_list) {
128 if (err) return next(err)
129
130 res.json(videos_list)
131 })
132}
133
134// ---------------------------------------------------------------------------
135
136// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
137function removeTorrent (magnetUri, callback) {
138 try {
139 webtorrent.remove(magnetUri, callback)
140 } catch (err) {
141 logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
142 return callback(null)
143 }
144}
diff --git a/server/controllers/index.js b/server/controllers/index.js
new file mode 100644
index 000000000..858f493da
--- /dev/null
+++ b/server/controllers/index.js
@@ -0,0 +1,11 @@
1'use strict'
2
3var constants = require('../initializers/constants')
4
5var apiController = require('./api/' + constants.API_VERSION)
6var viewsController = require('./views')
7
8module.exports = {
9 api: apiController,
10 views: viewsController
11}
diff --git a/server/controllers/views.js b/server/controllers/views.js
new file mode 100644
index 000000000..aa9718079
--- /dev/null
+++ b/server/controllers/views.js
@@ -0,0 +1,27 @@
1'use strict'
2
3var express = require('express')
4
5var cacheMiddleware = require('../middlewares').cache
6
7var router = express.Router()
8
9router.get(/^\/(index)?$/, cacheMiddleware.cache(), getIndex)
10router.get('/partials/:directory/:name', cacheMiddleware.cache(), getPartial)
11
12// ---------------------------------------------------------------------------
13
14module.exports = router
15
16// ---------------------------------------------------------------------------
17
18function getIndex (req, res) {
19 res.render('index')
20}
21
22function getPartial (req, res) {
23 var directory = req.params.directory
24 var name = req.params.name
25
26 res.render('partials/' + directory + '/' + name)
27}
diff --git a/server/helpers/customValidators.js b/server/helpers/customValidators.js
new file mode 100644
index 000000000..20c41f5da
--- /dev/null
+++ b/server/helpers/customValidators.js
@@ -0,0 +1,32 @@
1'use strict'
2
3var validator = require('validator')
4
5var customValidators = {
6 eachIsRemoteVideosAddValid: eachIsRemoteVideosAddValid,
7 eachIsRemoteVideosRemoveValid: eachIsRemoteVideosRemoveValid,
8 isArray: isArray
9}
10
11function eachIsRemoteVideosAddValid (values) {
12 return values.every(function (val) {
13 return validator.isLength(val.name, 1, 50) &&
14 validator.isLength(val.description, 1, 50) &&
15 validator.isLength(val.magnetUri, 10) &&
16 validator.isURL(val.podUrl)
17 })
18}
19
20function eachIsRemoteVideosRemoveValid (values) {
21 return values.every(function (val) {
22 return validator.isLength(val.magnetUri, 10)
23 })
24}
25
26function isArray (value) {
27 return Array.isArray(value)
28}
29
30// ---------------------------------------------------------------------------
31
32module.exports = customValidators
diff --git a/server/helpers/logger.js b/server/helpers/logger.js
new file mode 100644
index 000000000..67f69a875
--- /dev/null
+++ b/server/helpers/logger.js
@@ -0,0 +1,40 @@
1// Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/
2'use strict'
3
4var config = require('config')
5var path = require('path')
6var winston = require('winston')
7winston.emitErrs = true
8
9var logDir = path.join(__dirname, '..', config.get('storage.logs'))
10var logger = new winston.Logger({
11 transports: [
12 new winston.transports.File({
13 level: 'debug',
14 filename: path.join(logDir, 'all-logs.log'),
15 handleExceptions: true,
16 json: true,
17 maxsize: 5242880,
18 maxFiles: 5,
19 colorize: false
20 }),
21 new winston.transports.Console({
22 level: 'debug',
23 handleExceptions: true,
24 humanReadableUnhandledException: true,
25 json: false,
26 colorize: true
27 })
28 ],
29 exitOnError: true
30})
31
32logger.stream = {
33 write: function (message, encoding) {
34 logger.info(message)
35 }
36}
37
38// ---------------------------------------------------------------------------
39
40module.exports = logger
diff --git a/server/helpers/peertubeCrypto.js b/server/helpers/peertubeCrypto.js
new file mode 100644
index 000000000..29b9d79c9
--- /dev/null
+++ b/server/helpers/peertubeCrypto.js
@@ -0,0 +1,147 @@
1'use strict'
2
3var config = require('config')
4var crypto = require('crypto')
5var fs = require('fs')
6var openssl = require('openssl-wrapper')
7var path = require('path')
8var ursa = require('ursa')
9
10var logger = require('./logger')
11
12var certDir = path.join(__dirname, '..', config.get('storage.certs'))
13var algorithm = 'aes-256-ctr'
14
15var peertubeCrypto = {
16 checkSignature: checkSignature,
17 createCertsIfNotExist: createCertsIfNotExist,
18 decrypt: decrypt,
19 encrypt: encrypt,
20 getCertDir: getCertDir,
21 sign: sign
22}
23
24function checkSignature (public_key, raw_data, hex_signature) {
25 var crt = ursa.createPublicKey(public_key)
26 var is_valid = crt.hashAndVerify('sha256', new Buffer(raw_data).toString('hex'), hex_signature, 'hex')
27 return is_valid
28}
29
30function createCertsIfNotExist (callback) {
31 certsExist(function (exist) {
32 if (exist === true) {
33 return callback(null)
34 }
35
36 createCerts(function (err) {
37 return callback(err)
38 })
39 })
40}
41
42function decrypt (key, data, callback) {
43 fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) {
44 if (err) return callback(err)
45
46 var my_private_key = ursa.createPrivateKey(file)
47 var decrypted_key = my_private_key.decrypt(key, 'hex', 'utf8')
48 var decrypted_data = symetricDecrypt(data, decrypted_key)
49
50 return callback(null, decrypted_data)
51 })
52}
53
54function encrypt (public_key, data, callback) {
55 var crt = ursa.createPublicKey(public_key)
56
57 symetricEncrypt(data, function (err, dataEncrypted) {
58 if (err) return callback(err)
59
60 var key = crt.encrypt(dataEncrypted.password, 'utf8', 'hex')
61 var encrypted = {
62 data: dataEncrypted.crypted,
63 key: key
64 }
65
66 callback(null, encrypted)
67 })
68}
69
70function getCertDir () {
71 return certDir
72}
73
74function sign (data) {
75 var myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem'))
76 var signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex')
77
78 return signature
79}
80
81// ---------------------------------------------------------------------------
82
83module.exports = peertubeCrypto
84
85// ---------------------------------------------------------------------------
86
87function certsExist (callback) {
88 fs.exists(certDir + 'peertube.key.pem', function (exists) {
89 return callback(exists)
90 })
91}
92
93function createCerts (callback) {
94 certsExist(function (exist) {
95 if (exist === true) {
96 var string = 'Certs already exist.'
97 logger.warning(string)
98 return callback(new Error(string))
99 }
100
101 logger.info('Generating a RSA key...')
102 openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) {
103 if (err) {
104 logger.error('Cannot create private key on this pod.')
105 return callback(err)
106 }
107 logger.info('RSA key generated.')
108
109 logger.info('Manage public key...')
110 openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) {
111 if (err) {
112 logger.error('Cannot create public key on this pod.')
113 return callback(err)
114 }
115
116 logger.info('Public key managed.')
117 return callback(null)
118 })
119 })
120 })
121}
122
123function generatePassword (callback) {
124 crypto.randomBytes(32, function (err, buf) {
125 if (err) return callback(err)
126
127 callback(null, buf.toString('utf8'))
128 })
129}
130
131function symetricDecrypt (text, password) {
132 var decipher = crypto.createDecipher(algorithm, password)
133 var dec = decipher.update(text, 'hex', 'utf8')
134 dec += decipher.final('utf8')
135 return dec
136}
137
138function symetricEncrypt (text, callback) {
139 generatePassword(function (err, password) {
140 if (err) return callback(err)
141
142 var cipher = crypto.createCipher(algorithm, password)
143 var crypted = cipher.update(text, 'utf8', 'hex')
144 crypted += cipher.final('hex')
145 callback(null, { crypted: crypted, password: password })
146 })
147}
diff --git a/server/helpers/requests.js b/server/helpers/requests.js
new file mode 100644
index 000000000..e19afa5ca
--- /dev/null
+++ b/server/helpers/requests.js
@@ -0,0 +1,109 @@
1'use strict'
2
3var async = require('async')
4var config = require('config')
5var request = require('request')
6var replay = require('request-replay')
7
8var constants = require('../initializers/constants')
9var logger = require('./logger')
10var peertubeCrypto = require('./peertubeCrypto')
11
12var http = config.get('webserver.https') ? 'https' : 'http'
13var host = config.get('webserver.host')
14var port = config.get('webserver.port')
15
16var requests = {
17 makeMultipleRetryRequest: makeMultipleRetryRequest
18}
19
20function makeMultipleRetryRequest (all_data, pods, callbackEach, callback) {
21 if (!callback) {
22 callback = callbackEach
23 callbackEach = null
24 }
25
26 var url = http + '://' + host + ':' + port
27 var signature
28
29 // Add signature if it is specified in the params
30 if (all_data.method === 'POST' && all_data.data && all_data.sign === true) {
31 signature = peertubeCrypto.sign(url)
32 }
33
34 // Make a request for each pod
35 async.each(pods, function (pod, callback_each_async) {
36 function callbackEachRetryRequest (err, response, body, url, pod) {
37 if (callbackEach !== null) {
38 callbackEach(err, response, body, url, pod, function () {
39 callback_each_async()
40 })
41 } else {
42 callback_each_async()
43 }
44 }
45
46 var params = {
47 url: pod.url + all_data.path,
48 method: all_data.method
49 }
50
51 // Add data with POST requst ?
52 if (all_data.method === 'POST' && all_data.data) {
53 // Encrypt data ?
54 if (all_data.encrypt === true) {
55 // TODO: ES6 with let
56 ;(function (copy_params, copy_url, copy_pod, copy_signature) {
57 peertubeCrypto.encrypt(pod.publicKey, JSON.stringify(all_data.data), function (err, encrypted) {
58 if (err) return callback(err)
59
60 copy_params.json = {
61 data: encrypted.data,
62 key: encrypted.key
63 }
64
65 makeRetryRequest(copy_params, copy_url, copy_pod, copy_signature, callbackEachRetryRequest)
66 })
67 })(params, url, pod, signature)
68 } else {
69 params.json = { data: all_data.data }
70 makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
71 }
72 } else {
73 makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
74 }
75 }, callback)
76}
77
78// ---------------------------------------------------------------------------
79
80module.exports = requests
81
82// ---------------------------------------------------------------------------
83
84function makeRetryRequest (params, from_url, to_pod, signature, callbackEach) {
85 // Append the signature
86 if (signature) {
87 params.json.signature = {
88 url: from_url,
89 signature: signature
90 }
91 }
92
93 logger.debug('Make retry requests to %s.', to_pod.url)
94
95 replay(
96 request.post(params, function (err, response, body) {
97 callbackEach(err, response, body, params.url, to_pod)
98 }),
99 {
100 retries: constants.REQUEST_RETRIES,
101 factor: 3,
102 maxTimeout: Infinity,
103 errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
104 }
105 ).on('replay', function (replay) {
106 logger.info('Replaying request to %s. Request failed: %d %s. Replay number: #%d. Will retry in: %d ms.',
107 params.url, replay.error.code, replay.error.message, replay.number, replay.delay)
108 })
109}
diff --git a/server/helpers/utils.js b/server/helpers/utils.js
new file mode 100644
index 000000000..d2c9ad8b2
--- /dev/null
+++ b/server/helpers/utils.js
@@ -0,0 +1,16 @@
1'use strict'
2
3var logger = require('./logger')
4
5var utils = {
6 cleanForExit: cleanForExit
7}
8
9function cleanForExit (webtorrent_process) {
10 logger.info('Gracefully exiting.')
11 process.kill(-webtorrent_process.pid)
12}
13
14// ---------------------------------------------------------------------------
15
16module.exports = utils
diff --git a/server/initializers/checker.js b/server/initializers/checker.js
new file mode 100644
index 000000000..ec7bc0ad2
--- /dev/null
+++ b/server/initializers/checker.js
@@ -0,0 +1,46 @@
1'use strict'
2
3var config = require('config')
4var mkdirp = require('mkdirp')
5var path = require('path')
6
7var checker = {
8 checkConfig: checkConfig,
9 createDirectoriesIfNotExist: createDirectoriesIfNotExist
10}
11
12// Check the config files
13function checkConfig () {
14 var required = [ 'listen.port',
15 'webserver.https', 'webserver.host', 'webserver.port',
16 'database.host', 'database.port', 'database.suffix',
17 'storage.certs', 'storage.uploads', 'storage.logs',
18 'network.friends' ]
19 var miss = []
20
21 for (var key of required) {
22 if (!config.has(key)) {
23 miss.push(key)
24 }
25 }
26
27 return miss
28}
29
30// Create directories for the storage if it doesn't exist
31function createDirectoriesIfNotExist () {
32 var storages = config.get('storage')
33
34 for (var key of Object.keys(storages)) {
35 var dir = storages[key]
36 try {
37 mkdirp.sync(path.join(__dirname, '..', dir))
38 } catch (error) {
39 throw new Error('Cannot create ' + path + ':' + error)
40 }
41 }
42}
43
44// ---------------------------------------------------------------------------
45
46module.exports = checker
diff --git a/server/initializers/constants.js b/server/initializers/constants.js
new file mode 100644
index 000000000..16e50443b
--- /dev/null
+++ b/server/initializers/constants.js
@@ -0,0 +1,42 @@
1'use strict'
2
3// API version of our pod
4var API_VERSION = 'v1'
5
6// Score a pod has when we create it as a friend
7var FRIEND_BASE_SCORE = 100
8
9// Time to wait between requests to the friends
10var INTERVAL = 60000
11
12// Number of points we add/remove from a friend after a successful/bad request
13var PODS_SCORE = {
14 MALUS: -10,
15 BONUS: 10
16}
17
18// Number of retries we make for the make retry requests (to friends...)
19var REQUEST_RETRIES = 10
20
21// Special constants for a test instance
22if (isTestInstance() === true) {
23 FRIEND_BASE_SCORE = 20
24 INTERVAL = 10000
25 REQUEST_RETRIES = 2
26}
27
28// ---------------------------------------------------------------------------
29
30module.exports = {
31 API_VERSION: API_VERSION,
32 FRIEND_BASE_SCORE: FRIEND_BASE_SCORE,
33 INTERVAL: INTERVAL,
34 PODS_SCORE: PODS_SCORE,
35 REQUEST_RETRIES: REQUEST_RETRIES
36}
37
38// ---------------------------------------------------------------------------
39
40function isTestInstance () {
41 return (process.env.NODE_ENV === 'test')
42}
diff --git a/server/initializers/database.js b/server/initializers/database.js
new file mode 100644
index 000000000..a917442ec
--- /dev/null
+++ b/server/initializers/database.js
@@ -0,0 +1,29 @@
1'use strict'
2
3var config = require('config')
4var mongoose = require('mongoose')
5
6var logger = require('../helpers/logger')
7
8var dbname = 'peertube' + config.get('database.suffix')
9var host = config.get('database.host')
10var port = config.get('database.port')
11
12var database = {
13 connect: connect
14}
15
16function connect () {
17 mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname)
18 mongoose.connection.on('error', function () {
19 throw new Error('Mongodb connection error.')
20 })
21
22 mongoose.connection.on('open', function () {
23 logger.info('Connected to mongodb.')
24 })
25}
26
27// ---------------------------------------------------------------------------
28
29module.exports = database
diff --git a/server/lib/friends.js b/server/lib/friends.js
new file mode 100644
index 000000000..006a64404
--- /dev/null
+++ b/server/lib/friends.js
@@ -0,0 +1,228 @@
1'use strict'
2
3var async = require('async')
4var config = require('config')
5var fs = require('fs')
6var request = require('request')
7
8var constants = require('../initializers/constants')
9var logger = require('../helpers/logger')
10var peertubeCrypto = require('../helpers/peertubeCrypto')
11var Pods = require('../models/pods')
12var poolRequests = require('../lib/poolRequests')
13var requests = require('../helpers/requests')
14var Videos = require('../models/videos')
15
16var http = config.get('webserver.https') ? 'https' : 'http'
17var host = config.get('webserver.host')
18var port = config.get('webserver.port')
19
20var pods = {
21 addVideoToFriends: addVideoToFriends,
22 hasFriends: hasFriends,
23 makeFriends: makeFriends,
24 quitFriends: quitFriends,
25 removeVideoToFriends: removeVideoToFriends
26}
27
28function addVideoToFriends (video) {
29 // To avoid duplicates
30 var id = video.name + video.magnetUri
31 // ensure namePath is null
32 video.namePath = null
33 poolRequests.addRequest(id, 'add', video)
34}
35
36function hasFriends (callback) {
37 Pods.count(function (err, count) {
38 if (err) return callback(err)
39
40 var has_friends = (count !== 0)
41 callback(null, has_friends)
42 })
43}
44
45function makeFriends (callback) {
46 var pods_score = {}
47
48 logger.info('Make friends!')
49 fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
50 if (err) {
51 logger.error('Cannot read public cert.')
52 return callback(err)
53 }
54
55 var urls = config.get('network.friends')
56
57 async.each(urls, function (url, callback) {
58 computeForeignPodsList(url, pods_score, callback)
59 }, function (err) {
60 if (err) return callback(err)
61
62 logger.debug('Pods scores computed.', { pods_score: pods_score })
63 var pods_list = computeWinningPods(urls, pods_score)
64 logger.debug('Pods that we keep computed.', { pods_to_keep: pods_list })
65
66 makeRequestsToWinningPods(cert, pods_list, callback)
67 })
68 })
69}
70
71function quitFriends (callback) {
72 // Stop pool requests
73 poolRequests.deactivate()
74 // Flush pool requests
75 poolRequests.forceSend()
76
77 Pods.list(function (err, pods) {
78 if (err) return callback(err)
79
80 var request = {
81 method: 'POST',
82 path: '/api/' + constants.API_VERSION + '/pods/remove',
83 sign: true,
84 encrypt: true,
85 data: {
86 url: 'me' // Fake data
87 }
88 }
89
90 // Announce we quit them
91 requests.makeMultipleRetryRequest(request, pods, function () {
92 Pods.removeAll(function (err) {
93 poolRequests.activate()
94
95 if (err) return callback(err)
96
97 logger.info('Broke friends, so sad :(')
98
99 Videos.removeAllRemotes(function (err) {
100 if (err) return callback(err)
101
102 logger.info('Removed all remote videos.')
103 callback(null)
104 })
105 })
106 })
107 })
108}
109
110function removeVideoToFriends (video) {
111 // To avoid duplicates
112 var id = video.name + video.magnetUri
113 poolRequests.addRequest(id, 'remove', video)
114}
115
116// ---------------------------------------------------------------------------
117
118module.exports = pods
119
120// ---------------------------------------------------------------------------
121
122function computeForeignPodsList (url, pods_score, callback) {
123 // Let's give 1 point to the pod we ask the friends list
124 pods_score[url] = 1
125
126 getForeignPodsList(url, function (err, foreign_pods_list) {
127 if (err) return callback(err)
128 if (foreign_pods_list.length === 0) return callback()
129
130 async.each(foreign_pods_list, function (foreign_pod, callback_each) {
131 var foreign_url = foreign_pod.url
132
133 if (pods_score[foreign_url]) pods_score[foreign_url]++
134 else pods_score[foreign_url] = 1
135
136 callback_each()
137 }, function () {
138 callback()
139 })
140 })
141}
142
143function computeWinningPods (urls, pods_score) {
144 // Build the list of pods to add
145 // Only add a pod if it exists in more than a half base pods
146 var pods_list = []
147 var base_score = urls.length / 2
148 Object.keys(pods_score).forEach(function (pod) {
149 if (pods_score[pod] > base_score) pods_list.push({ url: pod })
150 })
151
152 return pods_list
153}
154
155function getForeignPodsList (url, callback) {
156 var path = '/api/' + constants.API_VERSION + '/pods'
157
158 request.get(url + path, function (err, response, body) {
159 if (err) return callback(err)
160
161 callback(null, JSON.parse(body))
162 })
163}
164
165function makeRequestsToWinningPods (cert, pods_list, callback) {
166 // Stop pool requests
167 poolRequests.deactivate()
168 // Flush pool requests
169 poolRequests.forceSend()
170
171 // Get the list of our videos to send to our new friends
172 Videos.listOwned(function (err, videos_list) {
173 if (err) {
174 logger.error('Cannot get the list of videos we own.')
175 return callback(err)
176 }
177
178 var data = {
179 url: http + '://' + host + ':' + port,
180 publicKey: cert,
181 videos: videos_list
182 }
183
184 requests.makeMultipleRetryRequest(
185 { method: 'POST', path: '/api/' + constants.API_VERSION + '/pods/', data: data },
186
187 pods_list,
188
189 function eachRequest (err, response, body, url, pod, callback_each_request) {
190 // We add the pod if it responded correctly with its public certificate
191 if (!err && response.statusCode === 200) {
192 Pods.add({ url: pod.url, publicKey: body.cert, score: constants.FRIEND_BASE_SCORE }, function (err) {
193 if (err) {
194 logger.error('Error with adding %s pod.', pod.url, { error: err })
195 return callback_each_request()
196 }
197
198 Videos.addRemotes(body.videos, function (err) {
199 if (err) {
200 logger.error('Error with adding videos of pod.', pod.url, { error: err })
201 return callback_each_request()
202 }
203
204 logger.debug('Adding remote videos from %s.', pod.url, { videos: body.videos })
205 return callback_each_request()
206 })
207 })
208 } else {
209 logger.error('Error with adding %s pod.', pod.url, { error: err || new Error('Status not 200') })
210 return callback_each_request()
211 }
212 },
213
214 function endRequests (err) {
215 // Now we made new friends, we can re activate the pool of requests
216 poolRequests.activate()
217
218 if (err) {
219 logger.error('There was some errors when we wanted to make friends.')
220 return callback(err)
221 }
222
223 logger.debug('makeRequestsToWinningPods finished.')
224 return callback(null)
225 }
226 )
227 })
228}
diff --git a/server/lib/poolRequests.js b/server/lib/poolRequests.js
new file mode 100644
index 000000000..f786c3c7a
--- /dev/null
+++ b/server/lib/poolRequests.js
@@ -0,0 +1,221 @@
1'use strict'
2
3var async = require('async')
4var pluck = require('lodash-node/compat/collection/pluck')
5
6var constants = require('../initializers/constants')
7var logger = require('../helpers/logger')
8var Pods = require('../models/pods')
9var PoolRequests = require('../models/poolRequests')
10var requests = require('../helpers/requests')
11var Videos = require('../models/videos')
12
13var timer = null
14
15var poolRequests = {
16 activate: activate,
17 addRequest: addRequest,
18 deactivate: deactivate,
19 forceSend: forceSend
20}
21
22function activate () {
23 logger.info('Pool requests activated.')
24 timer = setInterval(makePoolRequests, constants.INTERVAL)
25}
26
27function addRequest (id, type, request) {
28 logger.debug('Add request to the pool requests.', { id: id, type: type, request: request })
29
30 PoolRequests.findById(id, function (err, entity) {
31 if (err) {
32 logger.error('Cannot find one pool request.', { error: err })
33 return // Abort
34 }
35
36 if (entity) {
37 if (entity.type === type) {
38 logger.error('Cannot insert two same requests.')
39 return // Abort
40 }
41
42 // Remove the request of the other type
43 PoolRequests.removeRequestById(id, function (err) {
44 if (err) {
45 logger.error('Cannot remove a pool request.', { error: err })
46 return // Abort
47 }
48 })
49 } else {
50 PoolRequests.create(id, type, request, function (err) {
51 if (err) logger.error('Cannot create a pool request.', { error: err })
52 return // Abort
53 })
54 }
55 })
56}
57
58function deactivate () {
59 logger.info('Pool requests deactivated.')
60 clearInterval(timer)
61}
62
63function forceSend () {
64 logger.info('Force pool requests sending.')
65 makePoolRequests()
66}
67
68// ---------------------------------------------------------------------------
69
70module.exports = poolRequests
71
72// ---------------------------------------------------------------------------
73
74function makePoolRequest (type, requests_to_make, callback) {
75 if (!callback) callback = function () {}
76
77 Pods.list(function (err, pods) {
78 if (err) return callback(err)
79
80 var params = {
81 encrypt: true,
82 sign: true,
83 method: 'POST',
84 path: null,
85 data: requests_to_make
86 }
87
88 if (type === 'add') {
89 params.path = '/api/' + constants.API_VERSION + '/remotevideos/add'
90 } else if (type === 'remove') {
91 params.path = '/api/' + constants.API_VERSION + '/remotevideos/remove'
92 } else {
93 return callback(new Error('Unkown pool request type.'))
94 }
95
96 var bad_pods = []
97 var good_pods = []
98
99 requests.makeMultipleRetryRequest(params, pods, callbackEachPodFinished, callbackAllPodsFinished)
100
101 function callbackEachPodFinished (err, response, body, url, pod, callback_each_pod_finished) {
102 if (err || (response.statusCode !== 200 && response.statusCode !== 204)) {
103 bad_pods.push(pod._id)
104 logger.error('Error sending secure request to %s pod.', url, { error: err || new Error('Status code not 20x') })
105 } else {
106 good_pods.push(pod._id)
107 }
108
109 return callback_each_pod_finished()
110 }
111
112 function callbackAllPodsFinished (err) {
113 if (err) return callback(err)
114
115 updatePodsScore(good_pods, bad_pods)
116 callback(null)
117 }
118 })
119}
120
121function makePoolRequests () {
122 logger.info('Making pool requests to friends.')
123
124 PoolRequests.list(function (err, pool_requests) {
125 if (err) {
126 logger.error('Cannot get the list of pool requests.', { err: err })
127 return // Abort
128 }
129
130 if (pool_requests.length === 0) return
131
132 var requests_to_make = {
133 add: {
134 ids: [],
135 requests: []
136 },
137 remove: {
138 ids: [],
139 requests: []
140 }
141 }
142
143 async.each(pool_requests, function (pool_request, callback_each) {
144 if (pool_request.type === 'add') {
145 requests_to_make.add.requests.push(pool_request.request)
146 requests_to_make.add.ids.push(pool_request._id)
147 } else if (pool_request.type === 'remove') {
148 requests_to_make.remove.requests.push(pool_request.request)
149 requests_to_make.remove.ids.push(pool_request._id)
150 } else {
151 logger.error('Unkown pool request type.', { request_type: pool_request.type })
152 return // abort
153 }
154
155 callback_each()
156 }, function () {
157 // Send the add requests
158 if (requests_to_make.add.requests.length !== 0) {
159 makePoolRequest('add', requests_to_make.add.requests, function (err) {
160 if (err) logger.error('Errors when sent add pool requests.', { error: err })
161
162 PoolRequests.removeRequests(requests_to_make.add.ids)
163 })
164 }
165
166 // Send the remove requests
167 if (requests_to_make.remove.requests.length !== 0) {
168 makePoolRequest('remove', requests_to_make.remove.requests, function (err) {
169 if (err) logger.error('Errors when sent remove pool requests.', { error: err })
170
171 PoolRequests.removeRequests(requests_to_make.remove.ids)
172 })
173 }
174 })
175 })
176}
177
178function removeBadPods () {
179 Pods.findBadPods(function (err, pods) {
180 if (err) {
181 logger.error('Cannot find bad pods.', { error: err })
182 return // abort
183 }
184
185 if (pods.length === 0) return
186
187 var urls = pluck(pods, 'url')
188 var ids = pluck(pods, '_id')
189
190 Videos.removeAllRemotesOf(urls, function (err, r) {
191 if (err) {
192 logger.error('Cannot remove videos from a pod that we removing.', { error: err })
193 } else {
194 var videos_removed = r.result.n
195 logger.info('Removed %d videos.', videos_removed)
196 }
197
198 Pods.removeAllByIds(ids, function (err, r) {
199 if (err) {
200 logger.error('Cannot remove bad pods.', { error: err })
201 } else {
202 var pods_removed = r.result.n
203 logger.info('Removed %d pods.', pods_removed)
204 }
205 })
206 })
207 })
208}
209
210function updatePodsScore (good_pods, bad_pods) {
211 logger.info('Updating %d good pods and %d bad pods scores.', good_pods.length, bad_pods.length)
212
213 Pods.incrementScores(good_pods, constants.PODS_SCORE.BONUS, function (err) {
214 if (err) logger.error('Cannot increment scores of good pods.')
215 })
216
217 Pods.incrementScores(bad_pods, constants.PODS_SCORE.MALUS, function (err) {
218 if (err) logger.error('Cannot increment scores of bad pods.')
219 removeBadPods()
220 })
221}
diff --git a/server/lib/videos.js b/server/lib/videos.js
new file mode 100644
index 000000000..2d7d9500d
--- /dev/null
+++ b/server/lib/videos.js
@@ -0,0 +1,50 @@
1'use strict'
2
3var async = require('async')
4var config = require('config')
5var path = require('path')
6var webtorrent = require('../lib/webtorrent')
7
8var logger = require('../helpers/logger')
9var Videos = require('../models/videos')
10
11var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
12
13var videos = {
14 seed: seed,
15 seedAllExisting: seedAllExisting
16}
17
18function seed (path, callback) {
19 logger.info('Seeding %s...', path)
20
21 webtorrent.seed(path, function (torrent) {
22 logger.info('%s seeded (%s).', path, torrent.magnetURI)
23
24 return callback(null, torrent)
25 })
26}
27
28function seedAllExisting (callback) {
29 Videos.listOwned(function (err, videos_list) {
30 if (err) {
31 logger.error('Cannot get list of the videos to seed.')
32 return callback(err)
33 }
34
35 async.each(videos_list, function (video, each_callback) {
36 seed(uploadDir + video.namePath, function (err) {
37 if (err) {
38 logger.error('Cannot seed this video.')
39 return callback(err)
40 }
41
42 each_callback(null)
43 })
44 }, callback)
45 })
46}
47
48// ---------------------------------------------------------------------------
49
50module.exports = videos
diff --git a/server/lib/webtorrent.js b/server/lib/webtorrent.js
new file mode 100644
index 000000000..cb641fead
--- /dev/null
+++ b/server/lib/webtorrent.js
@@ -0,0 +1,157 @@
1'use strict'
2
3var config = require('config')
4var ipc = require('node-ipc')
5var pathUtils = require('path')
6var spawn = require('electron-spawn')
7
8var logger = require('../helpers/logger')
9
10var host = config.get('webserver.host')
11var port = config.get('webserver.port')
12var nodeKey = 'webtorrentnode' + port
13var processKey = 'webtorrentprocess' + port
14ipc.config.silent = true
15ipc.config.id = nodeKey
16
17var webtorrent = {
18 add: add,
19 app: null, // Pid of the app
20 create: create,
21 remove: remove,
22 seed: seed,
23 silent: false // Useful for beautiful tests
24}
25
26function create (options, callback) {
27 if (typeof options === 'function') {
28 callback = options
29 options = {}
30 }
31
32 // Override options
33 if (options.host) host = options.host
34 if (options.port) {
35 port = options.port
36 nodeKey = 'webtorrentnode' + port
37 processKey = 'webtorrentprocess' + port
38 ipc.config.id = nodeKey
39 }
40
41 ipc.serve(function () {
42 if (!webtorrent.silent) logger.info('IPC server ready.')
43
44 // Run a timeout of 30s after which we exit the process
45 var timeout_webtorrent_process = setTimeout(function () {
46 throw new Error('Timeout : cannot run the webtorrent process. Please ensure you have electron-prebuilt npm package installed with xvfb-run.')
47 }, 30000)
48
49 ipc.server.on(processKey + '.ready', function () {
50 if (!webtorrent.silent) logger.info('Webtorrent process ready.')
51 clearTimeout(timeout_webtorrent_process)
52 callback()
53 })
54
55 ipc.server.on(processKey + '.exception', function (data) {
56 throw new Error('Received exception error from webtorrent process.' + data.exception)
57 })
58
59 var webtorrent_process = spawn(pathUtils.join(__dirname, 'webtorrentProcess.js'), host, port, { detached: true })
60 webtorrent_process.stderr.on('data', function (data) {
61 // logger.debug('Webtorrent process stderr: ', data.toString())
62 })
63
64 webtorrent_process.stdout.on('data', function (data) {
65 // logger.debug('Webtorrent process:', data.toString())
66 })
67
68 webtorrent.app = webtorrent_process
69 })
70
71 ipc.server.start()
72}
73
74function seed (path, callback) {
75 var extension = pathUtils.extname(path)
76 var basename = pathUtils.basename(path, extension)
77 var data = {
78 _id: basename,
79 args: {
80 path: path
81 }
82 }
83
84 if (!webtorrent.silent) logger.debug('Node wants to seed %s.', data._id)
85
86 // Finish signal
87 var event_key = nodeKey + '.seedDone.' + data._id
88 ipc.server.on(event_key, function listener (received) {
89 if (!webtorrent.silent) logger.debug('Process seeded torrent %s.', received.magnetUri)
90
91 // This is a fake object, we just use the magnetUri in this project
92 var torrent = {
93 magnetURI: received.magnetUri
94 }
95
96 ipc.server.off(event_key)
97 callback(torrent)
98 })
99
100 ipc.server.broadcast(processKey + '.seed', data)
101}
102
103function add (magnetUri, callback) {
104 var data = {
105 _id: magnetUri,
106 args: {
107 magnetUri: magnetUri
108 }
109 }
110
111 if (!webtorrent.silent) logger.debug('Node wants to add ' + data._id)
112
113 // Finish signal
114 var event_key = nodeKey + '.addDone.' + data._id
115 ipc.server.on(event_key, function (received) {
116 if (!webtorrent.silent) logger.debug('Process added torrent.')
117
118 // This is a fake object, we just use the magnetUri in this project
119 var torrent = {
120 files: received.files
121 }
122
123 ipc.server.off(event_key)
124 callback(torrent)
125 })
126
127 ipc.server.broadcast(processKey + '.add', data)
128}
129
130function remove (magnetUri, callback) {
131 var data = {
132 _id: magnetUri,
133 args: {
134 magnetUri: magnetUri
135 }
136 }
137
138 if (!webtorrent.silent) logger.debug('Node wants to stop seeding %s.', data._id)
139
140 // Finish signal
141 var event_key = nodeKey + '.removeDone.' + data._id
142 ipc.server.on(event_key, function (received) {
143 if (!webtorrent.silent) logger.debug('Process removed torrent %s.', data._id)
144
145 var err = null
146 if (received.err) err = received.err
147
148 ipc.server.off(event_key)
149 callback(err)
150 })
151
152 ipc.server.broadcast(processKey + '.remove', data)
153}
154
155// ---------------------------------------------------------------------------
156
157module.exports = webtorrent
diff --git a/server/lib/webtorrentProcess.js b/server/lib/webtorrentProcess.js
new file mode 100644
index 000000000..7da52523a
--- /dev/null
+++ b/server/lib/webtorrentProcess.js
@@ -0,0 +1,92 @@
1'use strict'
2
3var WebTorrent = require('webtorrent')
4var ipc = require('node-ipc')
5
6function webtorrent (args) {
7 if (args.length !== 3) {
8 throw new Error('Wrong arguments number: ' + args.length + '/3')
9 }
10
11 var host = args[1]
12 var port = args[2]
13 var nodeKey = 'webtorrentnode' + port
14 var processKey = 'webtorrentprocess' + port
15
16 ipc.config.silent = true
17 ipc.config.id = processKey
18
19 if (host === 'client' && port === '1') global.WEBTORRENT_ANNOUNCE = []
20 else global.WEBTORRENT_ANNOUNCE = 'ws://' + host + ':' + port + '/tracker/socket'
21 var wt = new WebTorrent({ dht: false })
22
23 function seed (data) {
24 var args = data.args
25 var path = args.path
26 var _id = data._id
27
28 wt.seed(path, { announceList: '' }, function (torrent) {
29 var to_send = {
30 magnetUri: torrent.magnetURI
31 }
32
33 ipc.of[nodeKey].emit(nodeKey + '.seedDone.' + _id, to_send)
34 })
35 }
36
37 function add (data) {
38 var args = data.args
39 var magnetUri = args.magnetUri
40 var _id = data._id
41
42 wt.add(magnetUri, function (torrent) {
43 var to_send = {
44 files: []
45 }
46
47 torrent.files.forEach(function (file) {
48 to_send.files.push({ path: file.path })
49 })
50
51 ipc.of[nodeKey].emit(nodeKey + '.addDone.' + _id, to_send)
52 })
53 }
54
55 function remove (data) {
56 var args = data.args
57 var magnetUri = args.magnetUri
58 var _id = data._id
59
60 try {
61 wt.remove(magnetUri, callback)
62 } catch (err) {
63 console.log('Cannot remove the torrent from WebTorrent.')
64 return callback(null)
65 }
66
67 function callback () {
68 var to_send = {}
69 ipc.of[nodeKey].emit(nodeKey + '.removeDone.' + _id, to_send)
70 }
71 }
72
73 console.log('Configuration: ' + host + ':' + port)
74 console.log('Connecting to IPC...')
75
76 ipc.connectTo(nodeKey, function () {
77 ipc.of[nodeKey].on(processKey + '.seed', seed)
78 ipc.of[nodeKey].on(processKey + '.add', add)
79 ipc.of[nodeKey].on(processKey + '.remove', remove)
80
81 ipc.of[nodeKey].emit(processKey + '.ready')
82 console.log('Ready.')
83 })
84
85 process.on('uncaughtException', function (e) {
86 ipc.of[nodeKey].emit(processKey + '.exception', { exception: e })
87 })
88}
89
90// ---------------------------------------------------------------------------
91
92module.exports = webtorrent
diff --git a/server/middlewares/cache.js b/server/middlewares/cache.js
new file mode 100644
index 000000000..0d3da0075
--- /dev/null
+++ b/server/middlewares/cache.js
@@ -0,0 +1,23 @@
1'use strict'
2
3var cacheMiddleware = {
4 cache: cache
5}
6
7function cache (cache) {
8 return function (req, res, next) {
9 // If we want explicitly a cache
10 // Or if we don't specify if we want a cache or no and we are in production
11 if (cache === true || (cache !== false && process.env.NODE_ENV === 'production')) {
12 res.setHeader('Cache-Control', 'public')
13 } else {
14 res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
15 }
16
17 next()
18 }
19}
20
21// ---------------------------------------------------------------------------
22
23module.exports = cacheMiddleware
diff --git a/server/middlewares/index.js b/server/middlewares/index.js
new file mode 100644
index 000000000..c85899b0c
--- /dev/null
+++ b/server/middlewares/index.js
@@ -0,0 +1,15 @@
1'use strict'
2
3var cacheMiddleware = require('./cache')
4var reqValidatorsMiddleware = require('./reqValidators')
5var secureMiddleware = require('./secure')
6
7var middlewares = {
8 cache: cacheMiddleware,
9 reqValidators: reqValidatorsMiddleware,
10 secure: secureMiddleware
11}
12
13// ---------------------------------------------------------------------------
14
15module.exports = middlewares
diff --git a/server/middlewares/reqValidators/index.js b/server/middlewares/reqValidators/index.js
new file mode 100644
index 000000000..345dbd0e2
--- /dev/null
+++ b/server/middlewares/reqValidators/index.js
@@ -0,0 +1,15 @@
1'use strict'
2
3var podsReqValidators = require('./pods')
4var remoteReqValidators = require('./remote')
5var videosReqValidators = require('./videos')
6
7var reqValidators = {
8 pods: podsReqValidators,
9 remote: remoteReqValidators,
10 videos: videosReqValidators
11}
12
13// ---------------------------------------------------------------------------
14
15module.exports = reqValidators
diff --git a/server/middlewares/reqValidators/pods.js b/server/middlewares/reqValidators/pods.js
new file mode 100644
index 000000000..ef09d51cf
--- /dev/null
+++ b/server/middlewares/reqValidators/pods.js
@@ -0,0 +1,39 @@
1'use strict'
2
3var checkErrors = require('./utils').checkErrors
4var friends = require('../../lib/friends')
5var logger = require('../../helpers/logger')
6
7var reqValidatorsPod = {
8 makeFriends: makeFriends,
9 podsAdd: podsAdd
10}
11
12function makeFriends (req, res, next) {
13 friends.hasFriends(function (err, has_friends) {
14 if (err) {
15 logger.error('Cannot know if we have friends.', { error: err })
16 res.sendStatus(500)
17 }
18
19 if (has_friends === true) {
20 // We need to quit our friends before make new ones
21 res.sendStatus(409)
22 } else {
23 return next()
24 }
25 })
26}
27
28function podsAdd (req, res, next) {
29 req.checkBody('data.url', 'Should have an url').notEmpty().isURL({ require_protocol: true })
30 req.checkBody('data.publicKey', 'Should have a public key').notEmpty()
31
32 logger.debug('Checking podsAdd parameters', { parameters: req.body })
33
34 checkErrors(req, res, next)
35}
36
37// ---------------------------------------------------------------------------
38
39module.exports = reqValidatorsPod
diff --git a/server/middlewares/reqValidators/remote.js b/server/middlewares/reqValidators/remote.js
new file mode 100644
index 000000000..88de16b49
--- /dev/null
+++ b/server/middlewares/reqValidators/remote.js
@@ -0,0 +1,43 @@
1'use strict'
2
3var checkErrors = require('./utils').checkErrors
4var logger = require('../../helpers/logger')
5
6var reqValidatorsRemote = {
7 remoteVideosAdd: remoteVideosAdd,
8 remoteVideosRemove: remoteVideosRemove,
9 secureRequest: secureRequest
10}
11
12function remoteVideosAdd (req, res, next) {
13 req.checkBody('data').isArray()
14 req.checkBody('data').eachIsRemoteVideosAddValid()
15
16 logger.debug('Checking remoteVideosAdd parameters', { parameters: req.body })
17
18 checkErrors(req, res, next)
19}
20
21function remoteVideosRemove (req, res, next) {
22 req.checkBody('data').isArray()
23 req.checkBody('data').eachIsRemoteVideosRemoveValid()
24
25 logger.debug('Checking remoteVideosRemove parameters', { parameters: req.body })
26
27 checkErrors(req, res, next)
28}
29
30function secureRequest (req, res, next) {
31 req.checkBody('signature.url', 'Should have a signature url').isURL()
32 req.checkBody('signature.signature', 'Should have a signature').notEmpty()
33 req.checkBody('key', 'Should have a key').notEmpty()
34 req.checkBody('data', 'Should have data').notEmpty()
35
36 logger.debug('Checking secureRequest parameters', { parameters: { data: req.body.data, keyLength: req.body.key.length } })
37
38 checkErrors(req, res, next)
39}
40
41// ---------------------------------------------------------------------------
42
43module.exports = reqValidatorsRemote
diff --git a/server/middlewares/reqValidators/utils.js b/server/middlewares/reqValidators/utils.js
new file mode 100644
index 000000000..46c982571
--- /dev/null
+++ b/server/middlewares/reqValidators/utils.js
@@ -0,0 +1,25 @@
1'use strict'
2
3var util = require('util')
4
5var logger = require('../../helpers/logger')
6
7var reqValidatorsUtils = {
8 checkErrors: checkErrors
9}
10
11function checkErrors (req, res, next, status_code) {
12 if (status_code === undefined) status_code = 400
13 var errors = req.validationErrors()
14
15 if (errors) {
16 logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors })
17 return res.status(status_code).send('There have been validation errors: ' + util.inspect(errors))
18 }
19
20 return next()
21}
22
23// ---------------------------------------------------------------------------
24
25module.exports = reqValidatorsUtils
diff --git a/server/middlewares/reqValidators/videos.js b/server/middlewares/reqValidators/videos.js
new file mode 100644
index 000000000..4e5f4391f
--- /dev/null
+++ b/server/middlewares/reqValidators/videos.js
@@ -0,0 +1,74 @@
1'use strict'
2
3var checkErrors = require('./utils').checkErrors
4var logger = require('../../helpers/logger')
5var Videos = require('../../models/videos')
6
7var reqValidatorsVideos = {
8 videosAdd: videosAdd,
9 videosGet: videosGet,
10 videosRemove: videosRemove,
11 videosSearch: videosSearch
12}
13
14function videosAdd (req, res, next) {
15 req.checkFiles('input_video[0].originalname', 'Should have an input video').notEmpty()
16 req.checkFiles('input_video[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i)
17 req.checkBody('name', 'Should have a name').isLength(1, 50)
18 req.checkBody('description', 'Should have a description').isLength(1, 250)
19
20 logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
21
22 checkErrors(req, res, next)
23}
24
25function videosGet (req, res, next) {
26 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
27
28 logger.debug('Checking videosGet parameters', { parameters: req.params })
29
30 checkErrors(req, res, function () {
31 Videos.getVideoState(req.params.id, function (err, state) {
32 if (err) {
33 logger.error('Error in videosGet request validator.', { error: err })
34 res.sendStatus(500)
35 }
36
37 if (state.exist === false) return res.status(404).send('Video not found')
38
39 next()
40 })
41 })
42}
43
44function videosRemove (req, res, next) {
45 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
46
47 logger.debug('Checking videosRemove parameters', { parameters: req.params })
48
49 checkErrors(req, res, function () {
50 Videos.getVideoState(req.params.id, function (err, state) {
51 if (err) {
52 logger.error('Error in videosRemove request validator.', { error: err })
53 res.sendStatus(500)
54 }
55
56 if (state.exist === false) return res.status(404).send('Video not found')
57 else if (state.owned === false) return res.status(403).send('Cannot remove video of another pod')
58
59 next()
60 })
61 })
62}
63
64function videosSearch (req, res, next) {
65 req.checkParams('name', 'Should have a name').notEmpty()
66
67 logger.debug('Checking videosSearch parameters', { parameters: req.params })
68
69 checkErrors(req, res, next)
70}
71
72// ---------------------------------------------------------------------------
73
74module.exports = reqValidatorsVideos
diff --git a/server/middlewares/secure.js b/server/middlewares/secure.js
new file mode 100644
index 000000000..bfd28316a
--- /dev/null
+++ b/server/middlewares/secure.js
@@ -0,0 +1,49 @@
1'use strict'
2
3var logger = require('../helpers/logger')
4var peertubeCrypto = require('../helpers/peertubeCrypto')
5var Pods = require('../models/pods')
6
7var secureMiddleware = {
8 decryptBody: decryptBody
9}
10
11function decryptBody (req, res, next) {
12 var url = req.body.signature.url
13 Pods.findByUrl(url, function (err, pod) {
14 if (err) {
15 logger.error('Cannot get signed url in decryptBody.', { error: err })
16 return res.sendStatus(500)
17 }
18
19 if (pod === null) {
20 logger.error('Unknown pod %s.', url)
21 return res.sendStatus(403)
22 }
23
24 logger.debug('Decrypting body from %s.', url)
25
26 var signature_ok = peertubeCrypto.checkSignature(pod.publicKey, url, req.body.signature.signature)
27
28 if (signature_ok === true) {
29 peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) {
30 if (err) {
31 logger.error('Cannot decrypt data.', { error: err })
32 return res.sendStatus(500)
33 }
34
35 req.body.data = JSON.parse(decrypted)
36 delete req.body.key
37
38 next()
39 })
40 } else {
41 logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url)
42 return res.sendStatus(403)
43 }
44 })
45}
46
47// ---------------------------------------------------------------------------
48
49module.exports = secureMiddleware
diff --git a/server/models/pods.js b/server/models/pods.js
new file mode 100644
index 000000000..57ed20292
--- /dev/null
+++ b/server/models/pods.js
@@ -0,0 +1,88 @@
1'use strict'
2
3var mongoose = require('mongoose')
4
5var constants = require('../initializers/constants')
6var logger = require('../helpers/logger')
7
8// ---------------------------------------------------------------------------
9
10var podsSchema = mongoose.Schema({
11 url: String,
12 publicKey: String,
13 score: { type: Number, max: constants.FRIEND_BASE_SCORE }
14})
15var PodsDB = mongoose.model('pods', podsSchema)
16
17// ---------------------------------------------------------------------------
18
19var Pods = {
20 add: add,
21 count: count,
22 findByUrl: findByUrl,
23 findBadPods: findBadPods,
24 incrementScores: incrementScores,
25 list: list,
26 remove: remove,
27 removeAll: removeAll,
28 removeAllByIds: removeAllByIds
29}
30
31// TODO: check if the pod is not already a friend
32function add (data, callback) {
33 if (!callback) callback = function () {}
34 var params = {
35 url: data.url,
36 publicKey: data.publicKey,
37 score: constants.FRIEND_BASE_SCORE
38 }
39
40 PodsDB.create(params, callback)
41}
42
43function count (callback) {
44 return PodsDB.count(callback)
45}
46
47function findBadPods (callback) {
48 PodsDB.find({ score: 0 }, callback)
49}
50
51function findByUrl (url, callback) {
52 PodsDB.findOne({ url: url }, callback)
53}
54
55function incrementScores (ids, value, callback) {
56 if (!callback) callback = function () {}
57 PodsDB.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback)
58}
59
60function list (callback) {
61 PodsDB.find(function (err, pods_list) {
62 if (err) {
63 logger.error('Cannot get the list of the pods.')
64 return callback(err)
65 }
66
67 return callback(null, pods_list)
68 })
69}
70
71function remove (url, callback) {
72 if (!callback) callback = function () {}
73 PodsDB.remove({ url: url }, callback)
74}
75
76function removeAll (callback) {
77 if (!callback) callback = function () {}
78 PodsDB.remove(callback)
79}
80
81function removeAllByIds (ids, callback) {
82 if (!callback) callback = function () {}
83 PodsDB.remove({ _id: { $in: ids } }, callback)
84}
85
86// ---------------------------------------------------------------------------
87
88module.exports = Pods
diff --git a/server/models/poolRequests.js b/server/models/poolRequests.js
new file mode 100644
index 000000000..970315597
--- /dev/null
+++ b/server/models/poolRequests.js
@@ -0,0 +1,55 @@
1'use strict'
2
3var mongoose = require('mongoose')
4
5var logger = require('../helpers/logger')
6
7// ---------------------------------------------------------------------------
8
9var poolRequestsSchema = mongoose.Schema({
10 type: String,
11 id: String, // Special id to find duplicates (video created we want to remove...)
12 request: mongoose.Schema.Types.Mixed
13})
14var PoolRequestsDB = mongoose.model('poolRequests', poolRequestsSchema)
15
16// ---------------------------------------------------------------------------
17
18var PoolRequests = {
19 create: create,
20 findById: findById,
21 list: list,
22 removeRequestById: removeRequestById,
23 removeRequests: removeRequests
24}
25
26function create (id, type, request, callback) {
27 PoolRequestsDB.create({ id: id, type: type, request: request }, callback)
28}
29
30function findById (id, callback) {
31 PoolRequestsDB.findOne({ id: id }, callback)
32}
33
34function list (callback) {
35 PoolRequestsDB.find({}, { _id: 1, type: 1, request: 1 }, callback)
36}
37
38function removeRequestById (id, callback) {
39 PoolRequestsDB.remove({ id: id }, callback)
40}
41
42function removeRequests (ids) {
43 PoolRequestsDB.remove({ _id: { $in: ids } }, function (err) {
44 if (err) {
45 logger.error('Cannot remove requests from the pool requests database.', { error: err })
46 return // Abort
47 }
48
49 logger.info('Pool requests flushed.')
50 })
51}
52
53// ---------------------------------------------------------------------------
54
55module.exports = PoolRequests
diff --git a/server/models/videos.js b/server/models/videos.js
new file mode 100644
index 000000000..5e2eeae07
--- /dev/null
+++ b/server/models/videos.js
@@ -0,0 +1,234 @@
1'use strict'
2
3var async = require('async')
4var config = require('config')
5var dz = require('dezalgo')
6var fs = require('fs')
7var mongoose = require('mongoose')
8var path = require('path')
9
10var logger = require('../helpers/logger')
11
12var http = config.get('webserver.https') === true ? 'https' : 'http'
13var host = config.get('webserver.host')
14var port = config.get('webserver.port')
15var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
16
17// ---------------------------------------------------------------------------
18
19var videosSchema = mongoose.Schema({
20 name: String,
21 namePath: String,
22 description: String,
23 magnetUri: String,
24 podUrl: String
25})
26var VideosDB = mongoose.model('videos', videosSchema)
27
28// ---------------------------------------------------------------------------
29
30var Videos = {
31 add: add,
32 addRemotes: addRemotes,
33 get: get,
34 getVideoState: getVideoState,
35 isOwned: isOwned,
36 list: list,
37 listOwned: listOwned,
38 removeOwned: removeOwned,
39 removeAllRemotes: removeAllRemotes,
40 removeAllRemotesOf: removeAllRemotesOf,
41 removeRemotesOfByMagnetUris: removeRemotesOfByMagnetUris,
42 search: search
43}
44
45function add (video, callback) {
46 logger.info('Adding %s video to database.', video.name)
47
48 var params = video
49 params.podUrl = http + '://' + host + ':' + port
50
51 VideosDB.create(params, function (err, video) {
52 if (err) {
53 logger.error('Cannot insert this video into database.')
54 return callback(err)
55 }
56
57 callback(null)
58 })
59}
60
61// TODO: avoid doublons
62function addRemotes (videos, callback) {
63 if (!callback) callback = function () {}
64
65 var to_add = []
66
67 async.each(videos, function (video, callback_each) {
68 callback_each = dz(callback_each)
69 logger.debug('Add remote video from pod: %s', video.podUrl)
70
71 var params = {
72 name: video.name,
73 namePath: null,
74 description: video.description,
75 magnetUri: video.magnetUri,
76 podUrl: video.podUrl
77 }
78
79 to_add.push(params)
80
81 callback_each()
82 }, function () {
83 VideosDB.create(to_add, function (err, videos) {
84 if (err) {
85 logger.error('Cannot insert this remote video.')
86 return callback(err)
87 }
88
89 return callback(null, videos)
90 })
91 })
92}
93
94function get (id, callback) {
95 VideosDB.findById(id, function (err, video) {
96 if (err) {
97 logger.error('Cannot get this video.')
98 return callback(err)
99 }
100
101 return callback(null, video)
102 })
103}
104
105function getVideoState (id, callback) {
106 get(id, function (err, video) {
107 if (err) return callback(err)
108
109 var exist = (video !== null)
110 var owned = false
111 if (exist === true) {
112 owned = (video.namePath !== null)
113 }
114
115 return callback(null, { exist: exist, owned: owned })
116 })
117}
118
119function isOwned (id, callback) {
120 VideosDB.findById(id, function (err, video) {
121 if (err || !video) {
122 if (!err) err = new Error('Cannot find this video.')
123 logger.error('Cannot find this video.')
124 return callback(err)
125 }
126
127 if (video.namePath === null) {
128 var error_string = 'Cannot remove the video of another pod.'
129 logger.error(error_string)
130 return callback(new Error(error_string), false, video)
131 }
132
133 callback(null, true, video)
134 })
135}
136
137function list (callback) {
138 VideosDB.find(function (err, videos_list) {
139 if (err) {
140 logger.error('Cannot get the list of the videos.')
141 return callback(err)
142 }
143
144 return callback(null, videos_list)
145 })
146}
147
148function listOwned (callback) {
149 // If namePath is not null this is *our* video
150 VideosDB.find({ namePath: { $ne: null } }, function (err, videos_list) {
151 if (err) {
152 logger.error('Cannot get the list of owned videos.')
153 return callback(err)
154 }
155
156 return callback(null, videos_list)
157 })
158}
159
160function removeOwned (id, callback) {
161 VideosDB.findByIdAndRemove(id, function (err, video) {
162 if (err) {
163 logger.error('Cannot remove the torrent.')
164 return callback(err)
165 }
166
167 fs.unlink(uploadDir + video.namePath, function (err) {
168 if (err) {
169 logger.error('Cannot remove this video file.')
170 return callback(err)
171 }
172
173 callback(null)
174 })
175 })
176}
177
178function removeAllRemotes (callback) {
179 VideosDB.remove({ namePath: null }, callback)
180}
181
182function removeAllRemotesOf (fromUrl, callback) {
183 VideosDB.remove({ podUrl: fromUrl }, callback)
184}
185
186// Use the magnet Uri because the _id field is not the same on different servers
187function removeRemotesOfByMagnetUris (fromUrl, magnetUris, callback) {
188 if (callback === undefined) callback = function () {}
189
190 VideosDB.find({ magnetUri: { $in: magnetUris } }, function (err, videos) {
191 if (err || !videos) {
192 logger.error('Cannot find the torrent URI of these remote videos.')
193 return callback(err)
194 }
195
196 var to_remove = []
197 async.each(videos, function (video, callback_async) {
198 callback_async = dz(callback_async)
199
200 if (video.podUrl !== fromUrl) {
201 logger.error('The pod %s has not the rights on the video of %s.', fromUrl, video.podUrl)
202 } else {
203 to_remove.push(video._id)
204 }
205
206 callback_async()
207 }, function () {
208 VideosDB.remove({ _id: { $in: to_remove } }, function (err) {
209 if (err) {
210 logger.error('Cannot remove the remote videos.')
211 return callback(err)
212 }
213
214 logger.info('Removed remote videos from %s.', fromUrl)
215 callback(null)
216 })
217 })
218 })
219}
220
221function search (name, callback) {
222 VideosDB.find({ name: new RegExp(name) }, function (err, videos) {
223 if (err) {
224 logger.error('Cannot search the videos.')
225 return callback(err)
226 }
227
228 return callback(null, videos)
229 })
230}
231
232// ---------------------------------------------------------------------------
233
234module.exports = Videos
diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js
new file mode 100644
index 000000000..1c1ec71b3
--- /dev/null
+++ b/server/tests/api/checkParams.js
@@ -0,0 +1,300 @@
1'use strict'
2
3var async = require('async')
4var chai = require('chai')
5var expect = chai.expect
6var pathUtils = require('path')
7var request = require('supertest')
8
9var utils = require('./utils')
10
11describe('Test parameters validator', function () {
12 var app = null
13 var url = ''
14
15 function makePostRequest (path, fields, attach, done, fail) {
16 var status_code = 400
17 if (fail !== undefined && fail === false) status_code = 200
18
19 var req = request(url)
20 .post(path)
21 .set('Accept', 'application/json')
22
23 Object.keys(fields).forEach(function (field) {
24 var value = fields[field]
25 req.field(field, value)
26 })
27
28 req.expect(status_code, done)
29 }
30
31 function makePostBodyRequest (path, fields, done, fail) {
32 var status_code = 400
33 if (fail !== undefined && fail === false) status_code = 200
34
35 request(url)
36 .post(path)
37 .set('Accept', 'application/json')
38 .send(fields)
39 .expect(status_code, done)
40 }
41
42 // ---------------------------------------------------------------
43
44 before(function (done) {
45 this.timeout(20000)
46
47 async.series([
48 function (next) {
49 utils.flushTests(next)
50 },
51 function (next) {
52 utils.runServer(1, function (app1, url1) {
53 app = app1
54 url = url1
55 next()
56 })
57 }
58 ], done)
59 })
60
61 describe('Of the pods API', function () {
62 var path = '/api/v1/pods/'
63
64 describe('When adding a pod', function () {
65 it('Should fail with nothing', function (done) {
66 var data = {}
67 makePostBodyRequest(path, data, done)
68 })
69
70 it('Should fail without public key', function (done) {
71 var data = {
72 data: {
73 url: 'http://coucou.com'
74 }
75 }
76 makePostBodyRequest(path, data, done)
77 })
78
79 it('Should fail without an url', function (done) {
80 var data = {
81 data: {
82 publicKey: 'mysuperpublickey'
83 }
84 }
85 makePostBodyRequest(path, data, done)
86 })
87
88 it('Should fail with an incorrect url', function (done) {
89 var data = {
90 data: {
91 url: 'coucou.com',
92 publicKey: 'mysuperpublickey'
93 }
94 }
95 makePostBodyRequest(path, data, function () {
96 data.data.url = 'http://coucou'
97 makePostBodyRequest(path, data, function () {
98 data.data.url = 'coucou'
99 makePostBodyRequest(path, data, done)
100 })
101 })
102 })
103
104 it('Should succeed with the correct parameters', function (done) {
105 var data = {
106 data: {
107 url: 'http://coucou.com',
108 publicKey: 'mysuperpublickey'
109 }
110 }
111 makePostBodyRequest(path, data, done, false)
112 })
113 })
114 })
115
116 describe('Of the videos API', function () {
117 var path = '/api/v1/videos/'
118
119 describe('When searching a video', function () {
120 it('Should fail with nothing', function (done) {
121 request(url)
122 .get(pathUtils.join(path, 'search'))
123 .set('Accept', 'application/json')
124 .expect(400, done)
125 })
126 })
127
128 describe('When adding a video', function () {
129 it('Should fail with nothing', function (done) {
130 var data = {}
131 var attach = {}
132 makePostRequest(path, data, attach, done)
133 })
134
135 it('Should fail without name', function (done) {
136 var data = {
137 description: 'my super description'
138 }
139 var attach = {
140 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
141 }
142 makePostRequest(path, data, attach, done)
143 })
144
145 it('Should fail with a long name', function (done) {
146 var data = {
147 name: 'My very very very very very very very very very very very very very very very very long name',
148 description: 'my super description'
149 }
150 var attach = {
151 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
152 }
153 makePostRequest(path, data, attach, done)
154 })
155
156 it('Should fail without description', function (done) {
157 var data = {
158 name: 'my super name'
159 }
160 var attach = {
161 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
162 }
163 makePostRequest(path, data, attach, done)
164 })
165
166 it('Should fail with a long description', function (done) {
167 var data = {
168 name: 'my super name',
169 description: 'my super description which is very very very very very very very very very very very very very very' +
170 'very very very very very very very very very very very very very very very very very very very very very' +
171 'very very very very very very very very very very very very very very very long'
172 }
173 var attach = {
174 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
175 }
176 makePostRequest(path, data, attach, done)
177 })
178
179 it('Should fail without an input file', function (done) {
180 var data = {
181 name: 'my super name',
182 description: 'my super description'
183 }
184 var attach = {}
185 makePostRequest(path, data, attach, done)
186 })
187
188 it('Should fail without an incorrect input file', function (done) {
189 var data = {
190 name: 'my super name',
191 description: 'my super description'
192 }
193 var attach = {
194 'input_video': pathUtils.join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
195 }
196 makePostRequest(path, data, attach, done)
197 })
198
199 it('Should succeed with the correct parameters', function (done) {
200 var data = {
201 name: 'my super name',
202 description: 'my super description'
203 }
204 var attach = {
205 'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
206 }
207 makePostRequest(path, data, attach, function () {
208 attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4')
209 makePostRequest(path, data, attach, function () {
210 attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv')
211 makePostRequest(path, data, attach, done, true)
212 }, true)
213 }, true)
214 })
215 })
216
217 describe('When getting a video', function () {
218 it('Should return the list of the videos with nothing', function (done) {
219 request(url)
220 .get(path)
221 .set('Accept', 'application/json')
222 .expect(200)
223 .expect('Content-Type', /json/)
224 .end(function (err, res) {
225 if (err) throw err
226
227 expect(res.body).to.be.an('array')
228 expect(res.body.length).to.equal(0)
229
230 done()
231 })
232 })
233
234 it('Should fail without a mongodb id', function (done) {
235 request(url)
236 .get(path + 'coucou')
237 .set('Accept', 'application/json')
238 .expect(400, done)
239 })
240
241 it('Should return 404 with an incorrect video', function (done) {
242 request(url)
243 .get(path + '123456789012345678901234')
244 .set('Accept', 'application/json')
245 .expect(404, done)
246 })
247
248 it('Should succeed with the correct parameters')
249 })
250
251 describe('When removing a video', function () {
252 it('Should have 404 with nothing', function (done) {
253 request(url)
254 .delete(path)
255 .expect(404, done)
256 })
257
258 it('Should fail without a mongodb id', function (done) {
259 request(url)
260 .delete(path + 'hello')
261 .expect(400, done)
262 })
263
264 it('Should fail with a video which does not exist', function (done) {
265 request(url)
266 .delete(path + '123456789012345678901234')
267 .expect(404, done)
268 })
269
270 it('Should fail with a video of another pod')
271
272 it('Should succeed with the correct parameters')
273 })
274 })
275
276 describe('Of the remote videos API', function () {
277 describe('When making a secure request', function () {
278 it('Should check a secure request')
279 })
280
281 describe('When adding a video', function () {
282 it('Should check when adding a video')
283 })
284
285 describe('When removing a video', function () {
286 it('Should check when removing a video')
287 })
288 })
289
290 after(function (done) {
291 process.kill(-app.pid)
292
293 // Keep the logs if the test failed
294 if (this.ok) {
295 utils.flushTests(done)
296 } else {
297 done()
298 }
299 })
300})
diff --git a/server/tests/api/fixtures/video_short.mp4 b/server/tests/api/fixtures/video_short.mp4
new file mode 100644
index 000000000..35678362b
--- /dev/null
+++ b/server/tests/api/fixtures/video_short.mp4
Binary files differ
diff --git a/server/tests/api/fixtures/video_short.ogv b/server/tests/api/fixtures/video_short.ogv
new file mode 100644
index 000000000..9e253da82
--- /dev/null
+++ b/server/tests/api/fixtures/video_short.ogv
Binary files differ
diff --git a/server/tests/api/fixtures/video_short.webm b/server/tests/api/fixtures/video_short.webm
new file mode 100644
index 000000000..bf4b0ab6c
--- /dev/null
+++ b/server/tests/api/fixtures/video_short.webm
Binary files differ
diff --git a/server/tests/api/fixtures/video_short1.webm b/server/tests/api/fixtures/video_short1.webm
new file mode 100644
index 000000000..70ac0c644
--- /dev/null
+++ b/server/tests/api/fixtures/video_short1.webm
Binary files differ
diff --git a/server/tests/api/fixtures/video_short2.webm b/server/tests/api/fixtures/video_short2.webm
new file mode 100644
index 000000000..13d72dff7
--- /dev/null
+++ b/server/tests/api/fixtures/video_short2.webm
Binary files differ
diff --git a/server/tests/api/fixtures/video_short3.webm b/server/tests/api/fixtures/video_short3.webm
new file mode 100644
index 000000000..cde5dcd58
--- /dev/null
+++ b/server/tests/api/fixtures/video_short3.webm
Binary files differ
diff --git a/server/tests/api/fixtures/video_short_fake.webm b/server/tests/api/fixtures/video_short_fake.webm
new file mode 100644
index 000000000..d85290ae5
--- /dev/null
+++ b/server/tests/api/fixtures/video_short_fake.webm
@@ -0,0 +1 @@
this is a fake video mouahahah
diff --git a/server/tests/api/friendsAdvanced.js b/server/tests/api/friendsAdvanced.js
new file mode 100644
index 000000000..9838d890f
--- /dev/null
+++ b/server/tests/api/friendsAdvanced.js
@@ -0,0 +1,250 @@
1'use strict'
2
3var async = require('async')
4var chai = require('chai')
5var expect = chai.expect
6
7var utils = require('./utils')
8
9describe('Test advanced friends', function () {
10 var apps = []
11 var urls = []
12
13 function makeFriends (pod_number, callback) {
14 return utils.makeFriends(urls[pod_number - 1], callback)
15 }
16
17 function quitFriends (pod_number, callback) {
18 return utils.quitFriends(urls[pod_number - 1], callback)
19 }
20
21 function getFriendsList (pod_number, end) {
22 return utils.getFriendsList(urls[pod_number - 1], end)
23 }
24
25 function uploadVideo (pod_number, callback) {
26 var name = 'my super video'
27 var description = 'my super description'
28 var fixture = 'video_short.webm'
29
30 return utils.uploadVideo(urls[pod_number - 1], name, description, fixture, callback)
31 }
32
33 function getVideos (pod_number, callback) {
34 return utils.getVideosList(urls[pod_number - 1], callback)
35 }
36
37 // ---------------------------------------------------------------
38
39 before(function (done) {
40 this.timeout(30000)
41 utils.flushAndRunMultipleServers(6, function (apps_run, urls_run) {
42 apps = apps_run
43 urls = urls_run
44 done()
45 })
46 })
47
48 it('Should make friends with two pod each in a different group', function (done) {
49 this.timeout(20000)
50
51 async.series([
52 // Pod 3 makes friend with the first one
53 function (next) {
54 makeFriends(3, next)
55 },
56 // Pod 4 makes friend with the second one
57 function (next) {
58 makeFriends(4, next)
59 },
60 // Now if the fifth wants to make friends with the third et the first
61 function (next) {
62 makeFriends(5, next)
63 },
64 function (next) {
65 setTimeout(next, 11000)
66 }],
67 function (err) {
68 if (err) throw err
69
70 // It should have 0 friends
71 getFriendsList(5, function (err, res) {
72 if (err) throw err
73
74 expect(res.body.length).to.equal(0)
75
76 done()
77 })
78 }
79 )
80 })
81
82 it('Should quit all friends', function (done) {
83 this.timeout(10000)
84
85 async.series([
86 function (next) {
87 quitFriends(1, next)
88 },
89 function (next) {
90 quitFriends(2, next)
91 }],
92 function (err) {
93 if (err) throw err
94
95 async.each([ 1, 2, 3, 4, 5, 6 ], function (i, callback) {
96 getFriendsList(i, function (err, res) {
97 if (err) throw err
98
99 expect(res.body.length).to.equal(0)
100
101 callback()
102 })
103 }, done)
104 }
105 )
106 })
107
108 it('Should make friends with the pods 1, 2, 3', function (done) {
109 this.timeout(150000)
110
111 async.series([
112 // Pods 1, 2, 3 and 4 become friends
113 function (next) {
114 makeFriends(2, next)
115 },
116 function (next) {
117 makeFriends(1, next)
118 },
119 function (next) {
120 makeFriends(4, next)
121 },
122 // Kill pod 4
123 function (next) {
124 apps[3].kill()
125 next()
126 },
127 // Expulse pod 4 from pod 1 and 2
128 function (next) {
129 uploadVideo(1, next)
130 },
131 function (next) {
132 uploadVideo(2, next)
133 },
134 function (next) {
135 setTimeout(next, 11000)
136 },
137 function (next) {
138 uploadVideo(1, next)
139 },
140 function (next) {
141 uploadVideo(2, next)
142 },
143 function (next) {
144 setTimeout(next, 20000)
145 },
146 // Rerun server 4
147 function (next) {
148 utils.runServer(4, function (app, url) {
149 apps[3] = app
150 next()
151 })
152 },
153 function (next) {
154 getFriendsList(4, function (err, res) {
155 if (err) throw err
156
157 // Pod 4 didn't know pod 1 and 2 removed it
158 expect(res.body.length).to.equal(3)
159
160 next()
161 })
162 },
163 // Pod 6 ask pod 1, 2 and 3
164 function (next) {
165 makeFriends(6, next)
166 }],
167 function (err) {
168 if (err) throw err
169
170 getFriendsList(6, function (err, res) {
171 if (err) throw err
172
173 // Pod 4 should not be our friend
174 var result = res.body
175 expect(result.length).to.equal(3)
176 for (var pod of result) {
177 expect(pod.url).not.equal(urls[3])
178 }
179
180 done()
181 })
182 }
183 )
184 })
185
186 it('Should pod 1 quit friends', function (done) {
187 this.timeout(25000)
188
189 async.series([
190 // Upload a video on server 3 for aditionnal tests
191 function (next) {
192 uploadVideo(3, next)
193 },
194 function (next) {
195 setTimeout(next, 15000)
196 },
197 function (next) {
198 quitFriends(1, next)
199 },
200 // Remove pod 1 from pod 2
201 function (next) {
202 getVideos(1, function (err, res) {
203 if (err) throw err
204 expect(res.body).to.be.an('array')
205 expect(res.body.length).to.equal(2)
206
207 next()
208 })
209 }],
210 function (err) {
211 if (err) throw err
212
213 getVideos(2, function (err, res) {
214 if (err) throw err
215 expect(res.body).to.be.an('array')
216 expect(res.body.length).to.equal(3)
217 done()
218 })
219 }
220 )
221 })
222
223 it('Should make friends between pod 1 and 2 and exchange their videos', function (done) {
224 this.timeout(20000)
225 makeFriends(1, function () {
226 setTimeout(function () {
227 getVideos(1, function (err, res) {
228 if (err) throw err
229
230 expect(res.body).to.be.an('array')
231 expect(res.body.length).to.equal(5)
232
233 done()
234 })
235 }, 5000)
236 })
237 })
238
239 after(function (done) {
240 apps.forEach(function (app) {
241 process.kill(-app.pid)
242 })
243
244 if (this.ok) {
245 utils.flushTests(done)
246 } else {
247 done()
248 }
249 })
250})
diff --git a/server/tests/api/friendsBasic.js b/server/tests/api/friendsBasic.js
new file mode 100644
index 000000000..328724936
--- /dev/null
+++ b/server/tests/api/friendsBasic.js
@@ -0,0 +1,185 @@
1'use strict'
2
3var async = require('async')
4var chai = require('chai')
5var expect = chai.expect
6var request = require('supertest')
7
8var utils = require('./utils')
9
10describe('Test basic friends', function () {
11 var apps = []
12 var urls = []
13
14 function testMadeFriends (urls, url_to_test, callback) {
15 var friends = []
16 for (var i = 0; i < urls.length; i++) {
17 if (urls[i] === url_to_test) continue
18 friends.push(urls[i])
19 }
20
21 utils.getFriendsList(url_to_test, function (err, res) {
22 if (err) throw err
23
24 var result = res.body
25 var result_urls = [ result[0].url, result[1].url ]
26 expect(result).to.be.an('array')
27 expect(result.length).to.equal(2)
28 expect(result_urls[0]).to.not.equal(result_urls[1])
29
30 var error_string = 'Friends url do not correspond for ' + url_to_test
31 expect(friends).to.contain(result_urls[0], error_string)
32 expect(friends).to.contain(result_urls[1], error_string)
33 callback()
34 })
35 }
36
37 // ---------------------------------------------------------------
38
39 before(function (done) {
40 this.timeout(20000)
41 utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
42 apps = apps_run
43 urls = urls_run
44 done()
45 })
46 })
47
48 it('Should not have friends', function (done) {
49 async.each(urls, function (url, callback) {
50 utils.getFriendsList(url, function (err, res) {
51 if (err) throw err
52
53 var result = res.body
54 expect(result).to.be.an('array')
55 expect(result.length).to.equal(0)
56 callback()
57 })
58 }, done)
59 })
60
61 it('Should make friends', function (done) {
62 this.timeout(10000)
63
64 var path = '/api/v1/pods/makefriends'
65
66 async.series([
67 // The second pod make friend with the third
68 function (next) {
69 request(urls[1])
70 .get(path)
71 .set('Accept', 'application/json')
72 .expect(204)
73 .end(next)
74 },
75 // Wait for the request between pods
76 function (next) {
77 setTimeout(next, 1000)
78 },
79 // The second pod should have the third as a friend
80 function (next) {
81 utils.getFriendsList(urls[1], function (err, res) {
82 if (err) throw err
83
84 var result = res.body
85 expect(result).to.be.an('array')
86 expect(result.length).to.equal(1)
87 expect(result[0].url).to.be.equal(urls[2])
88
89 next()
90 })
91 },
92 // Same here, the third pod should have the second pod as a friend
93 function (next) {
94 utils.getFriendsList(urls[2], function (err, res) {
95 if (err) throw err
96
97 var result = res.body
98 expect(result).to.be.an('array')
99 expect(result.length).to.equal(1)
100 expect(result[0].url).to.be.equal(urls[1])
101
102 next()
103 })
104 },
105 // Finally the first pod make friend with the second pod
106 function (next) {
107 request(urls[0])
108 .get(path)
109 .set('Accept', 'application/json')
110 .expect(204)
111 .end(next)
112 },
113 // Wait for the request between pods
114 function (next) {
115 setTimeout(next, 1000)
116 }
117 ],
118 // Now each pod should be friend with the other ones
119 function (err) {
120 if (err) throw err
121 async.each(urls, function (url, callback) {
122 testMadeFriends(urls, url, callback)
123 }, done)
124 })
125 })
126
127 it('Should not be allowed to make friend again', function (done) {
128 utils.makeFriends(urls[1], 409, done)
129 })
130
131 it('Should quit friends of pod 2', function (done) {
132 async.series([
133 // Pod 1 quit friends
134 function (next) {
135 utils.quitFriends(urls[1], next)
136 },
137 // Pod 1 should not have friends anymore
138 function (next) {
139 utils.getFriendsList(urls[1], function (err, res) {
140 if (err) throw err
141
142 var result = res.body
143 expect(result).to.be.an('array')
144 expect(result.length).to.equal(0)
145
146 next()
147 })
148 },
149 // Other pods shouldn't have pod 1 too
150 function (next) {
151 async.each([ urls[0], urls[2] ], function (url, callback) {
152 utils.getFriendsList(url, function (err, res) {
153 if (err) throw err
154
155 var result = res.body
156 expect(result).to.be.an('array')
157 expect(result.length).to.equal(1)
158 expect(result[0].url).not.to.be.equal(urls[1])
159 callback()
160 })
161 }, next)
162 }
163 ], done)
164 })
165
166 it('Should allow pod 2 to make friend again', function (done) {
167 utils.makeFriends(urls[1], function () {
168 async.each(urls, function (url, callback) {
169 testMadeFriends(urls, url, callback)
170 }, done)
171 })
172 })
173
174 after(function (done) {
175 apps.forEach(function (app) {
176 process.kill(-app.pid)
177 })
178
179 if (this.ok) {
180 utils.flushTests(done)
181 } else {
182 done()
183 }
184 })
185})
diff --git a/server/tests/api/index.js b/server/tests/api/index.js
new file mode 100644
index 000000000..9c4fdd48a
--- /dev/null
+++ b/server/tests/api/index.js
@@ -0,0 +1,8 @@
1'use strict'
2
3// Order of the tests we want to execute
4require('./checkParams')
5require('./friendsBasic')
6require('./singlePod')
7require('./multiplePods')
8require('./friendsAdvanced')
diff --git a/server/tests/api/multiplePods.js b/server/tests/api/multiplePods.js
new file mode 100644
index 000000000..9fdd0f308
--- /dev/null
+++ b/server/tests/api/multiplePods.js
@@ -0,0 +1,328 @@
1'use strict'
2
3var async = require('async')
4var chai = require('chai')
5var expect = chai.expect
6var pathUtils = require('path')
7
8var utils = require('./utils')
9var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
10webtorrent.silent = true
11
12describe('Test multiple pods', function () {
13 var apps = []
14 var urls = []
15 var to_remove = []
16
17 before(function (done) {
18 this.timeout(30000)
19
20 async.series([
21 // Run servers
22 function (next) {
23 utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
24 apps = apps_run
25 urls = urls_run
26 next()
27 })
28 },
29 // The second pod make friend with the third
30 function (next) {
31 utils.makeFriends(urls[1], next)
32 },
33 // Wait for the request between pods
34 function (next) {
35 setTimeout(next, 10000)
36 },
37 // Pod 1 make friends too
38 function (next) {
39 utils.makeFriends(urls[0], next)
40 },
41 function (next) {
42 webtorrent.create({ host: 'client', port: '1' }, next)
43 }
44 ], done)
45 })
46
47 it('Should not have videos for all pods', function (done) {
48 async.each(urls, function (url, callback) {
49 utils.getVideosList(url, function (err, res) {
50 if (err) throw err
51
52 expect(res.body).to.be.an('array')
53 expect(res.body.length).to.equal(0)
54
55 callback()
56 })
57 }, done)
58 })
59
60 describe('Should upload the video and propagate on each pod', function () {
61 it('Should upload the video on pod 1 and propagate on each pod', function (done) {
62 this.timeout(15000)
63
64 async.series([
65 function (next) {
66 utils.uploadVideo(urls[0], 'my super name for pod 1', 'my super description for pod 1', 'video_short1.webm', next)
67 },
68 function (next) {
69 setTimeout(next, 11000)
70 }],
71 // All pods should have this video
72 function (err) {
73 if (err) throw err
74
75 async.each(urls, function (url, callback) {
76 var base_magnet = null
77
78 utils.getVideosList(url, function (err, res) {
79 if (err) throw err
80
81 var videos = res.body
82 expect(videos).to.be.an('array')
83 expect(videos.length).to.equal(1)
84 var video = videos[0]
85 expect(video.name).to.equal('my super name for pod 1')
86 expect(video.description).to.equal('my super description for pod 1')
87 expect(video.podUrl).to.equal('http://localhost:9001')
88 expect(video.magnetUri).to.exist
89
90 // All pods should have the same magnet Uri
91 if (base_magnet === null) {
92 base_magnet = video.magnetUri
93 } else {
94 expect(video.magnetUri).to.equal.magnetUri
95 }
96
97 callback()
98 })
99 }, done)
100 }
101 )
102 })
103
104 it('Should upload the video on pod 2 and propagate on each pod', function (done) {
105 this.timeout(15000)
106
107 async.series([
108 function (next) {
109 utils.uploadVideo(urls[1], 'my super name for pod 2', 'my super description for pod 2', 'video_short2.webm', next)
110 },
111 function (next) {
112 setTimeout(next, 11000)
113 }],
114 // All pods should have this video
115 function (err) {
116 if (err) throw err
117
118 async.each(urls, function (url, callback) {
119 var base_magnet = null
120
121 utils.getVideosList(url, function (err, res) {
122 if (err) throw err
123
124 var videos = res.body
125 expect(videos).to.be.an('array')
126 expect(videos.length).to.equal(2)
127 var video = videos[1]
128 expect(video.name).to.equal('my super name for pod 2')
129 expect(video.description).to.equal('my super description for pod 2')
130 expect(video.podUrl).to.equal('http://localhost:9002')
131 expect(video.magnetUri).to.exist
132
133 // All pods should have the same magnet Uri
134 if (base_magnet === null) {
135 base_magnet = video.magnetUri
136 } else {
137 expect(video.magnetUri).to.equal.magnetUri
138 }
139
140 callback()
141 })
142 }, done)
143 }
144 )
145 })
146
147 it('Should upload two videos on pod 3 and propagate on each pod', function (done) {
148 this.timeout(30000)
149
150 async.series([
151 function (next) {
152 utils.uploadVideo(urls[2], 'my super name for pod 3', 'my super description for pod 3', 'video_short3.webm', next)
153 },
154 function (next) {
155 utils.uploadVideo(urls[2], 'my super name for pod 3-2', 'my super description for pod 3-2', 'video_short.webm', next)
156 },
157 function (next) {
158 setTimeout(next, 22000)
159 }],
160 function (err) {
161 if (err) throw err
162
163 var base_magnet = null
164 // All pods should have this video
165 async.each(urls, function (url, callback) {
166 utils.getVideosList(url, function (err, res) {
167 if (err) throw err
168
169 var videos = res.body
170 expect(videos).to.be.an('array')
171 expect(videos.length).to.equal(4)
172 var video = videos[2]
173 expect(video.name).to.equal('my super name for pod 3')
174 expect(video.description).to.equal('my super description for pod 3')
175 expect(video.podUrl).to.equal('http://localhost:9003')
176 expect(video.magnetUri).to.exist
177
178 video = videos[3]
179 expect(video.name).to.equal('my super name for pod 3-2')
180 expect(video.description).to.equal('my super description for pod 3-2')
181 expect(video.podUrl).to.equal('http://localhost:9003')
182 expect(video.magnetUri).to.exist
183
184 // All pods should have the same magnet Uri
185 if (base_magnet === null) {
186 base_magnet = video.magnetUri
187 } else {
188 expect(video.magnetUri).to.equal.magnetUri
189 }
190
191 callback()
192 })
193 }, done)
194 }
195 )
196 })
197 })
198
199 describe('Should seed the uploaded video', function () {
200 it('Should add the file 1 by asking pod 3', function (done) {
201 // Yes, this could be long
202 this.timeout(200000)
203
204 utils.getVideosList(urls[2], function (err, res) {
205 if (err) throw err
206
207 var video = res.body[0]
208 to_remove.push(res.body[2]._id)
209 to_remove.push(res.body[3]._id)
210
211 webtorrent.add(video.magnetUri, function (torrent) {
212 expect(torrent.files).to.exist
213 expect(torrent.files.length).to.equal(1)
214 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
215
216 done()
217 })
218 })
219 })
220
221 it('Should add the file 2 by asking pod 1', function (done) {
222 // Yes, this could be long
223 this.timeout(200000)
224
225 utils.getVideosList(urls[0], function (err, res) {
226 if (err) throw err
227
228 var video = res.body[1]
229
230 webtorrent.add(video.magnetUri, function (torrent) {
231 expect(torrent.files).to.exist
232 expect(torrent.files.length).to.equal(1)
233 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
234
235 done()
236 })
237 })
238 })
239
240 it('Should add the file 3 by asking pod 2', function (done) {
241 // Yes, this could be long
242 this.timeout(200000)
243
244 utils.getVideosList(urls[1], function (err, res) {
245 if (err) throw err
246
247 var video = res.body[2]
248
249 webtorrent.add(video.magnetUri, function (torrent) {
250 expect(torrent.files).to.exist
251 expect(torrent.files.length).to.equal(1)
252 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
253
254 done()
255 })
256 })
257 })
258
259 it('Should add the file 3-2 by asking pod 1', function (done) {
260 // Yes, this could be long
261 this.timeout(200000)
262
263 utils.getVideosList(urls[0], function (err, res) {
264 if (err) throw err
265
266 var video = res.body[3]
267
268 webtorrent.add(video.magnetUri, function (torrent) {
269 expect(torrent.files).to.exist
270 expect(torrent.files.length).to.equal(1)
271 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
272
273 done()
274 })
275 })
276 })
277
278 it('Should remove the file 3 and 3-2 by asking pod 3', function (done) {
279 this.timeout(15000)
280
281 async.series([
282 function (next) {
283 utils.removeVideo(urls[2], to_remove[0], next)
284 },
285 function (next) {
286 utils.removeVideo(urls[2], to_remove[1], next)
287 }],
288 function (err) {
289 if (err) throw err
290 setTimeout(done, 11000)
291 }
292 )
293 })
294
295 it('Should have videos 1 and 3 on each pod', function (done) {
296 async.each(urls, function (url, callback) {
297 utils.getVideosList(url, function (err, res) {
298 if (err) throw err
299
300 var videos = res.body
301 expect(videos).to.be.an('array')
302 expect(videos.length).to.equal(2)
303 expect(videos[0]._id).not.to.equal(videos[1]._id)
304 expect(videos[0]._id).not.to.equal(to_remove[0])
305 expect(videos[1]._id).not.to.equal(to_remove[0])
306 expect(videos[0]._id).not.to.equal(to_remove[1])
307 expect(videos[1]._id).not.to.equal(to_remove[1])
308
309 callback()
310 })
311 }, done)
312 })
313 })
314
315 after(function (done) {
316 apps.forEach(function (app) {
317 process.kill(-app.pid)
318 })
319 process.kill(-webtorrent.app.pid)
320
321 // Keep the logs if the test failed
322 if (this.ok) {
323 utils.flushTests(done)
324 } else {
325 done()
326 }
327 })
328})
diff --git a/server/tests/api/singlePod.js b/server/tests/api/singlePod.js
new file mode 100644
index 000000000..3dd72c01b
--- /dev/null
+++ b/server/tests/api/singlePod.js
@@ -0,0 +1,146 @@
1'use strict'
2
3var async = require('async')
4var chai = require('chai')
5var expect = chai.expect
6var fs = require('fs')
7var pathUtils = require('path')
8
9var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
10webtorrent.silent = true
11
12var utils = require('./utils')
13
14describe('Test a single pod', function () {
15 var app = null
16 var url = ''
17 var video_id = -1
18
19 before(function (done) {
20 this.timeout(20000)
21
22 async.series([
23 function (next) {
24 utils.flushTests(next)
25 },
26 function (next) {
27 utils.runServer(1, function (app1, url1) {
28 app = app1
29 url = url1
30 next()
31 })
32 },
33 function (next) {
34 webtorrent.create({ host: 'client', port: '1' }, next)
35 }
36 ], done)
37 })
38
39 it('Should not have videos', function (done) {
40 utils.getVideosList(url, function (err, res) {
41 if (err) throw err
42
43 expect(res.body).to.be.an('array')
44 expect(res.body.length).to.equal(0)
45
46 done()
47 })
48 })
49
50 it('Should upload the video', function (done) {
51 this.timeout(5000)
52 utils.uploadVideo(url, 'my super name', 'my super description', 'video_short.webm', done)
53 })
54
55 it('Should seed the uploaded video', function (done) {
56 // Yes, this could be long
57 this.timeout(60000)
58
59 utils.getVideosList(url, function (err, res) {
60 if (err) throw err
61
62 expect(res.body).to.be.an('array')
63 expect(res.body.length).to.equal(1)
64
65 var video = res.body[0]
66 expect(video.name).to.equal('my super name')
67 expect(video.description).to.equal('my super description')
68 expect(video.podUrl).to.equal('http://localhost:9001')
69 expect(video.magnetUri).to.exist
70
71 video_id = video._id
72
73 webtorrent.add(video.magnetUri, function (torrent) {
74 expect(torrent.files).to.exist
75 expect(torrent.files.length).to.equal(1)
76 expect(torrent.files[0].path).to.exist.and.to.not.equal('')
77
78 done()
79 })
80 })
81 })
82
83 it('Should search the video', function (done) {
84 utils.searchVideo(url, 'my', function (err, res) {
85 if (err) throw err
86
87 expect(res.body).to.be.an('array')
88 expect(res.body.length).to.equal(1)
89
90 var video = res.body[0]
91 expect(video.name).to.equal('my super name')
92 expect(video.description).to.equal('my super description')
93 expect(video.podUrl).to.equal('http://localhost:9001')
94 expect(video.magnetUri).to.exist
95
96 done()
97 })
98 })
99
100 it('Should not find a search', function (done) {
101 utils.searchVideo(url, 'hello', function (err, res) {
102 if (err) throw err
103
104 expect(res.body).to.be.an('array')
105 expect(res.body.length).to.equal(0)
106
107 done()
108 })
109 })
110
111 it('Should remove the video', function (done) {
112 utils.removeVideo(url, video_id, function (err) {
113 if (err) throw err
114
115 fs.readdir(pathUtils.join(__dirname, '../../test1/uploads/'), function (err, files) {
116 if (err) throw err
117
118 expect(files.length).to.equal(0)
119 done()
120 })
121 })
122 })
123
124 it('Should not have videos', function (done) {
125 utils.getVideosList(url, function (err, res) {
126 if (err) throw err
127
128 expect(res.body).to.be.an('array')
129 expect(res.body.length).to.equal(0)
130
131 done()
132 })
133 })
134
135 after(function (done) {
136 process.kill(-app.pid)
137 process.kill(-webtorrent.app.pid)
138
139 // Keep the logs if the test failed
140 if (this.ok) {
141 utils.flushTests(done)
142 } else {
143 done()
144 }
145 })
146})
diff --git a/server/tests/api/utils.js b/server/tests/api/utils.js
new file mode 100644
index 000000000..47b706294
--- /dev/null
+++ b/server/tests/api/utils.js
@@ -0,0 +1,185 @@
1'use strict'
2
3var child_process = require('child_process')
4var exec = child_process.exec
5var fork = child_process.fork
6var pathUtils = require('path')
7var request = require('supertest')
8
9var testUtils = {
10 flushTests: flushTests,
11 getFriendsList: getFriendsList,
12 getVideosList: getVideosList,
13 makeFriends: makeFriends,
14 quitFriends: quitFriends,
15 removeVideo: removeVideo,
16 flushAndRunMultipleServers: flushAndRunMultipleServers,
17 runServer: runServer,
18 searchVideo: searchVideo,
19 uploadVideo: uploadVideo
20}
21
22// ---------------------- Export functions --------------------
23
24function flushTests (callback) {
25 exec(pathUtils.join(__dirname, '../../scripts/clean_test.sh'), callback)
26}
27
28function getFriendsList (url, end) {
29 var path = '/api/v1/pods/'
30
31 request(url)
32 .get(path)
33 .set('Accept', 'application/json')
34 .expect(200)
35 .expect('Content-Type', /json/)
36 .end(end)
37}
38
39function getVideosList (url, end) {
40 var path = '/api/v1/videos'
41
42 request(url)
43 .get(path)
44 .set('Accept', 'application/json')
45 .expect(200)
46 .expect('Content-Type', /json/)
47 .end(end)
48}
49
50function makeFriends (url, expected_status, callback) {
51 if (!callback) {
52 callback = expected_status
53 expected_status = 204
54 }
55
56 var path = '/api/v1/pods/makefriends'
57
58 // The first pod make friend with the third
59 request(url)
60 .get(path)
61 .set('Accept', 'application/json')
62 .expect(expected_status)
63 .end(function (err, res) {
64 if (err) throw err
65
66 // Wait for the request between pods
67 setTimeout(callback, 1000)
68 })
69}
70
71function quitFriends (url, callback) {
72 var path = '/api/v1/pods/quitfriends'
73
74 // The first pod make friend with the third
75 request(url)
76 .get(path)
77 .set('Accept', 'application/json')
78 .expect(204)
79 .end(function (err, res) {
80 if (err) throw err
81
82 // Wait for the request between pods
83 setTimeout(callback, 1000)
84 })
85}
86
87function removeVideo (url, id, end) {
88 var path = '/api/v1/videos'
89
90 request(url)
91 .delete(path + '/' + id)
92 .set('Accept', 'application/json')
93 .expect(204)
94 .end(end)
95}
96
97function flushAndRunMultipleServers (total_servers, serversRun) {
98 var apps = []
99 var urls = []
100 var i = 0
101
102 function anotherServerDone (number, app, url) {
103 apps[number - 1] = app
104 urls[number - 1] = url
105 i++
106 if (i === total_servers) {
107 serversRun(apps, urls)
108 }
109 }
110
111 flushTests(function () {
112 for (var j = 1; j <= total_servers; j++) {
113 (function (k) { // TODO: ES6 with let
114 // For the virtual buffer
115 setTimeout(function () {
116 runServer(k, function (app, url) {
117 anotherServerDone(k, app, url)
118 })
119 }, 1000 * k)
120 })(j)
121 }
122 })
123}
124
125function runServer (number, callback) {
126 var port = 9000 + number
127 var server_run_string = {
128 'Connected to mongodb': false,
129 'Server listening on port': false
130 }
131
132 // Share the environment
133 var env = Object.create(process.env)
134 env.NODE_ENV = 'test'
135 env.NODE_APP_INSTANCE = number
136 var options = {
137 silent: true,
138 env: env,
139 detached: true
140 }
141
142 var app = fork(pathUtils.join(__dirname, '../../server.js'), [], options)
143 app.stdout.on('data', function onStdout (data) {
144 var dont_continue = false
145 // Check if all required sentences are here
146 for (var key of Object.keys(server_run_string)) {
147 if (data.toString().indexOf(key) !== -1) server_run_string[key] = true
148 if (server_run_string[key] === false) dont_continue = true
149 }
150
151 // If no, there is maybe one thing not already initialized (mongodb...)
152 if (dont_continue === true) return
153
154 app.stdout.removeListener('data', onStdout)
155 callback(app, 'http://localhost:' + port)
156 })
157}
158
159function searchVideo (url, search, end) {
160 var path = '/api/v1/videos'
161
162 request(url)
163 .get(path + '/search/' + search)
164 .set('Accept', 'application/json')
165 .expect(200)
166 .expect('Content-Type', /json/)
167 .end(end)
168}
169
170function uploadVideo (url, name, description, fixture, end) {
171 var path = '/api/v1/videos'
172
173 request(url)
174 .post(path)
175 .set('Accept', 'application/json')
176 .field('name', name)
177 .field('description', description)
178 .attach('input_video', pathUtils.join(__dirname, 'fixtures', fixture))
179 .expect(201)
180 .end(end)
181}
182
183// ---------------------------------------------------------------------------
184
185module.exports = testUtils
diff --git a/server/tests/index.js b/server/tests/index.js
new file mode 100644
index 000000000..ccebbfe51
--- /dev/null
+++ b/server/tests/index.js
@@ -0,0 +1,6 @@
1;(function () {
2 'use strict'
3
4 // Order of the tests we want to execute
5 require('./api/')
6})()