aboutsummaryrefslogtreecommitdiffhomepage
path: root/server
diff options
context:
space:
mode:
authorChocobozzz <florian.bigard@gmail.com>2016-12-11 21:50:51 +0100
committerChocobozzz <florian.bigard@gmail.com>2016-12-19 21:22:28 +0100
commitfeb4bdfd9b46e87aadfa7c0d5338cde887d1f58c (patch)
tree2abc9fbc9569760e218fd52835850b757344b420 /server
parent108626609eda75e4ecc0a83a650a4d53c46220e0 (diff)
downloadPeerTube-feb4bdfd9b46e87aadfa7c0d5338cde887d1f58c.tar.gz
PeerTube-feb4bdfd9b46e87aadfa7c0d5338cde887d1f58c.tar.zst
PeerTube-feb4bdfd9b46e87aadfa7c0d5338cde887d1f58c.zip
First version with PostgreSQL
Diffstat (limited to 'server')
-rw-r--r--server/controllers/api/clients.js8
-rw-r--r--server/controllers/api/pods.js15
-rw-r--r--server/controllers/api/remote.js95
-rw-r--r--server/controllers/api/requests.js10
-rw-r--r--server/controllers/api/users.js28
-rw-r--r--server/controllers/api/videos.js63
-rw-r--r--server/controllers/client.js11
-rw-r--r--server/helpers/custom-validators/videos.js19
-rw-r--r--server/helpers/logger.js6
-rw-r--r--server/initializers/checker.js8
-rw-r--r--server/initializers/constants.js19
-rw-r--r--server/initializers/database.js58
-rw-r--r--server/initializers/installer.js29
-rw-r--r--server/initializers/migrator.js12
-rw-r--r--server/lib/friends.js73
-rw-r--r--server/lib/oauth-model.js32
-rw-r--r--server/middlewares/pods.js1
-rw-r--r--server/middlewares/secure.js6
-rw-r--r--server/middlewares/sort.js4
-rw-r--r--server/middlewares/validators/users.js13
-rw-r--r--server/middlewares/validators/videos.js17
-rw-r--r--server/models/application.js45
-rw-r--r--server/models/author.js28
-rw-r--r--server/models/oauth-client.js72
-rw-r--r--server/models/oauth-token.js109
-rw-r--r--server/models/pods.js156
-rw-r--r--server/models/request.js187
-rw-r--r--server/models/requestToPod.js30
-rw-r--r--server/models/user.js132
-rw-r--r--server/models/utils.js31
-rw-r--r--server/models/video.js388
-rw-r--r--server/tests/api/check-params.js6
-rw-r--r--server/tests/api/friends-basic.js4
-rw-r--r--server/tests/api/multiple-pods.js8
-rw-r--r--server/tests/api/requests.js29
-rw-r--r--server/tests/api/single-pod.js16
-rw-r--r--server/tests/api/users.js8
-rw-r--r--server/tests/utils/servers.js4
-rw-r--r--server/tests/utils/videos.js2
39 files changed, 1113 insertions, 669 deletions
diff --git a/server/controllers/api/clients.js b/server/controllers/api/clients.js
index 7755f6c2b..cf83cb835 100644
--- a/server/controllers/api/clients.js
+++ b/server/controllers/api/clients.js
@@ -1,13 +1,11 @@
1'use strict' 1'use strict'
2 2
3const express = require('express') 3const express = require('express')
4const mongoose = require('mongoose')
5 4
6const constants = require('../../initializers/constants') 5const constants = require('../../initializers/constants')
6const db = require('../../initializers/database')
7const logger = require('../../helpers/logger') 7const logger = require('../../helpers/logger')
8 8
9const Client = mongoose.model('OAuthClient')
10
11const router = express.Router() 9const router = express.Router()
12 10
13router.get('/local', getLocalClient) 11router.get('/local', getLocalClient)
@@ -27,12 +25,12 @@ function getLocalClient (req, res, next) {
27 return res.type('json').status(403).end() 25 return res.type('json').status(403).end()
28 } 26 }
29 27
30 Client.loadFirstClient(function (err, client) { 28 db.OAuthClient.loadFirstClient(function (err, client) {
31 if (err) return next(err) 29 if (err) return next(err)
32 if (!client) return next(new Error('No client available.')) 30 if (!client) return next(new Error('No client available.'))
33 31
34 res.json({ 32 res.json({
35 client_id: client._id, 33 client_id: client.clientId,
36 client_secret: client.clientSecret 34 client_secret: client.clientSecret
37 }) 35 })
38 }) 36 })
diff --git a/server/controllers/api/pods.js b/server/controllers/api/pods.js
index 7857fcee0..79f3f9d8d 100644
--- a/server/controllers/api/pods.js
+++ b/server/controllers/api/pods.js
@@ -1,9 +1,9 @@
1'use strict' 1'use strict'
2 2
3const express = require('express') 3const express = require('express')
4const mongoose = require('mongoose')
5const waterfall = require('async/waterfall') 4const waterfall = require('async/waterfall')
6 5
6const db = require('../../initializers/database')
7const logger = require('../../helpers/logger') 7const logger = require('../../helpers/logger')
8const friends = require('../../lib/friends') 8const friends = require('../../lib/friends')
9const middlewares = require('../../middlewares') 9const middlewares = require('../../middlewares')
@@ -15,7 +15,6 @@ const validators = middlewares.validators.pods
15const signatureValidator = middlewares.validators.remote.signature 15const signatureValidator = middlewares.validators.remote.signature
16 16
17const router = express.Router() 17const router = express.Router()
18const Pod = mongoose.model('Pod')
19 18
20router.get('/', listPods) 19router.get('/', listPods)
21router.post('/', 20router.post('/',
@@ -53,15 +52,15 @@ function addPods (req, res, next) {
53 52
54 waterfall([ 53 waterfall([
55 function addPod (callback) { 54 function addPod (callback) {
56 const pod = new Pod(informations) 55 const pod = db.Pod.build(informations)
57 pod.save(function (err, podCreated) { 56 pod.save().asCallback(function (err, podCreated) {
58 // Be sure about the number of parameters for the callback 57 // Be sure about the number of parameters for the callback
59 return callback(err, podCreated) 58 return callback(err, podCreated)
60 }) 59 })
61 }, 60 },
62 61
63 function sendMyVideos (podCreated, callback) { 62 function sendMyVideos (podCreated, callback) {
64 friends.sendOwnedVideosToPod(podCreated._id) 63 friends.sendOwnedVideosToPod(podCreated.id)
65 64
66 callback(null) 65 callback(null)
67 }, 66 },
@@ -84,7 +83,7 @@ function addPods (req, res, next) {
84} 83}
85 84
86function listPods (req, res, next) { 85function listPods (req, res, next) {
87 Pod.list(function (err, podsList) { 86 db.Pod.list(function (err, podsList) {
88 if (err) return next(err) 87 if (err) return next(err)
89 88
90 res.json(getFormatedPods(podsList)) 89 res.json(getFormatedPods(podsList))
@@ -111,11 +110,11 @@ function removePods (req, res, next) {
111 110
112 waterfall([ 111 waterfall([
113 function loadPod (callback) { 112 function loadPod (callback) {
114 Pod.loadByHost(host, callback) 113 db.Pod.loadByHost(host, callback)
115 }, 114 },
116 115
117 function removePod (pod, callback) { 116 function removePod (pod, callback) {
118 pod.remove(callback) 117 pod.destroy().asCallback(callback)
119 } 118 }
120 ], function (err) { 119 ], function (err) {
121 if (err) return next(err) 120 if (err) return next(err)
diff --git a/server/controllers/api/remote.js b/server/controllers/api/remote.js
index f1046c534..d856576a9 100644
--- a/server/controllers/api/remote.js
+++ b/server/controllers/api/remote.js
@@ -3,15 +3,15 @@
3const each = require('async/each') 3const each = require('async/each')
4const eachSeries = require('async/eachSeries') 4const eachSeries = require('async/eachSeries')
5const express = require('express') 5const express = require('express')
6const mongoose = require('mongoose') 6const waterfall = require('async/waterfall')
7 7
8const db = require('../../initializers/database')
8const middlewares = require('../../middlewares') 9const middlewares = require('../../middlewares')
9const secureMiddleware = middlewares.secure 10const secureMiddleware = middlewares.secure
10const validators = middlewares.validators.remote 11const validators = middlewares.validators.remote
11const logger = require('../../helpers/logger') 12const logger = require('../../helpers/logger')
12 13
13const router = express.Router() 14const router = express.Router()
14const Video = mongoose.model('Video')
15 15
16router.post('/videos', 16router.post('/videos',
17 validators.signature, 17 validators.signature,
@@ -53,34 +53,99 @@ function remoteVideos (req, res, next) {
53function addRemoteVideo (videoToCreateData, fromHost, callback) { 53function addRemoteVideo (videoToCreateData, fromHost, callback) {
54 logger.debug('Adding remote video "%s".', videoToCreateData.name) 54 logger.debug('Adding remote video "%s".', videoToCreateData.name)
55 55
56 const video = new Video(videoToCreateData) 56 waterfall([
57 video.podHost = fromHost 57
58 Video.generateThumbnailFromBase64(video, videoToCreateData.thumbnailBase64, function (err) { 58 function findOrCreatePod (callback) {
59 if (err) { 59 fromHost
60 logger.error('Cannot generate thumbnail from base 64 data.', { error: err }) 60
61 return callback(err) 61 const query = {
62 where: {
63 host: fromHost
64 },
65 defaults: {
66 host: fromHost
67 }
68 }
69
70 db.Pod.findOrCreate(query).asCallback(function (err, result) {
71 // [ instance, wasCreated ]
72 return callback(err, result[0])
73 })
74 },
75
76 function findOrCreateAuthor (pod, callback) {
77 const username = videoToCreateData.author
78
79 const query = {
80 where: {
81 name: username,
82 podId: pod.id
83 },
84 defaults: {
85 name: username,
86 podId: pod.id
87 }
88 }
89
90 db.Author.findOrCreate(query).asCallback(function (err, result) {
91 // [ instance, wasCreated ]
92 return callback(err, result[0])
93 })
94 },
95
96 function createVideoObject (author, callback) {
97 const videoData = {
98 name: videoToCreateData.name,
99 remoteId: videoToCreateData.remoteId,
100 extname: videoToCreateData.extname,
101 infoHash: videoToCreateData.infoHash,
102 description: videoToCreateData.description,
103 authorId: author.id,
104 duration: videoToCreateData.duration,
105 tags: videoToCreateData.tags
106 }
107
108 const video = db.Video.build(videoData)
109
110 return callback(null, video)
111 },
112
113 function generateThumbnail (video, callback) {
114 db.Video.generateThumbnailFromBase64(video, videoToCreateData.thumbnailBase64, function (err) {
115 if (err) {
116 logger.error('Cannot generate thumbnail from base 64 data.', { error: err })
117 return callback(err)
118 }
119
120 video.save().asCallback(callback)
121 })
122 },
123
124 function insertIntoDB (video, callback) {
125 video.save().asCallback(callback)
62 } 126 }
63 127
64 video.save(callback) 128 ], callback)
65 })
66} 129}
67 130
68function removeRemoteVideo (videoToRemoveData, fromHost, callback) { 131function removeRemoteVideo (videoToRemoveData, fromHost, callback) {
132 // TODO: use bulkDestroy?
133
69 // We need the list because we have to remove some other stuffs (thumbnail etc) 134 // We need the list because we have to remove some other stuffs (thumbnail etc)
70 Video.listByHostAndRemoteId(fromHost, videoToRemoveData.remoteId, function (err, videosList) { 135 db.Video.listByHostAndRemoteId(fromHost, videoToRemoveData.remoteId, function (err, videosList) {
71 if (err) { 136 if (err) {
72 logger.error('Cannot list videos from host and magnets.', { error: err }) 137 logger.error('Cannot list videos from host and remote id.', { error: err.message })
73 return callback(err) 138 return callback(err)
74 } 139 }
75 140
76 if (videosList.length === 0) { 141 if (videosList.length === 0) {
77 logger.error('No remote video was found for this pod.', { magnetUri: videoToRemoveData.magnetUri, podHost: fromHost }) 142 logger.error('No remote video was found for this pod.', { remoteId: videoToRemoveData.remoteId, podHost: fromHost })
78 } 143 }
79 144
80 each(videosList, function (video, callbackEach) { 145 each(videosList, function (video, callbackEach) {
81 logger.debug('Removing remote video %s.', video.magnetUri) 146 logger.debug('Removing remote video %s.', video.remoteId)
82 147
83 video.remove(callbackEach) 148 video.destroy().asCallback(callbackEach)
84 }, callback) 149 }, callback)
85 }) 150 })
86} 151}
diff --git a/server/controllers/api/requests.js b/server/controllers/api/requests.js
index 52aad6997..1f9193fc8 100644
--- a/server/controllers/api/requests.js
+++ b/server/controllers/api/requests.js
@@ -1,15 +1,13 @@
1'use strict' 1'use strict'
2 2
3const express = require('express') 3const express = require('express')
4const mongoose = require('mongoose')
5 4
6const constants = require('../../initializers/constants') 5const constants = require('../../initializers/constants')
6const db = require('../../initializers/database')
7const middlewares = require('../../middlewares') 7const middlewares = require('../../middlewares')
8const admin = middlewares.admin 8const admin = middlewares.admin
9const oAuth = middlewares.oauth 9const oAuth = middlewares.oauth
10 10
11const Request = mongoose.model('Request')
12
13const router = express.Router() 11const router = express.Router()
14 12
15router.get('/stats', 13router.get('/stats',
@@ -25,13 +23,13 @@ module.exports = router
25// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
26 24
27function getStatsRequests (req, res, next) { 25function getStatsRequests (req, res, next) {
28 Request.list(function (err, requests) { 26 db.Request.countTotalRequests(function (err, totalRequests) {
29 if (err) return next(err) 27 if (err) return next(err)
30 28
31 return res.json({ 29 return res.json({
32 requests: requests, 30 totalRequests: totalRequests,
33 maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL, 31 maxRequestsInParallel: constants.REQUESTS_IN_PARALLEL,
34 remainingMilliSeconds: Request.remainingMilliSeconds(), 32 remainingMilliSeconds: db.Request.remainingMilliSeconds(),
35 milliSecondsInterval: constants.REQUESTS_INTERVAL 33 milliSecondsInterval: constants.REQUESTS_INTERVAL
36 }) 34 })
37 }) 35 })
diff --git a/server/controllers/api/users.js b/server/controllers/api/users.js
index b4d687312..890028b36 100644
--- a/server/controllers/api/users.js
+++ b/server/controllers/api/users.js
@@ -2,10 +2,10 @@
2 2
3const each = require('async/each') 3const each = require('async/each')
4const express = require('express') 4const express = require('express')
5const mongoose = require('mongoose')
6const waterfall = require('async/waterfall') 5const waterfall = require('async/waterfall')
7 6
8const constants = require('../../initializers/constants') 7const constants = require('../../initializers/constants')
8const db = require('../../initializers/database')
9const friends = require('../../lib/friends') 9const friends = require('../../lib/friends')
10const logger = require('../../helpers/logger') 10const logger = require('../../helpers/logger')
11const middlewares = require('../../middlewares') 11const middlewares = require('../../middlewares')
@@ -17,9 +17,6 @@ const validatorsPagination = middlewares.validators.pagination
17const validatorsSort = middlewares.validators.sort 17const validatorsSort = middlewares.validators.sort
18const validatorsUsers = middlewares.validators.users 18const validatorsUsers = middlewares.validators.users
19 19
20const User = mongoose.model('User')
21const Video = mongoose.model('Video')
22
23const router = express.Router() 20const router = express.Router()
24 21
25router.get('/me', oAuth.authenticate, getUserInformation) 22router.get('/me', oAuth.authenticate, getUserInformation)
@@ -62,13 +59,13 @@ module.exports = router
62// --------------------------------------------------------------------------- 59// ---------------------------------------------------------------------------
63 60
64function createUser (req, res, next) { 61function createUser (req, res, next) {
65 const user = new User({ 62 const user = db.User.build({
66 username: req.body.username, 63 username: req.body.username,
67 password: req.body.password, 64 password: req.body.password,
68 role: constants.USER_ROLES.USER 65 role: constants.USER_ROLES.USER
69 }) 66 })
70 67
71 user.save(function (err, createdUser) { 68 user.save().asCallback(function (err, createdUser) {
72 if (err) return next(err) 69 if (err) return next(err)
73 70
74 return res.type('json').status(204).end() 71 return res.type('json').status(204).end()
@@ -76,7 +73,7 @@ function createUser (req, res, next) {
76} 73}
77 74
78function getUserInformation (req, res, next) { 75function getUserInformation (req, res, next) {
79 User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { 76 db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
80 if (err) return next(err) 77 if (err) return next(err)
81 78
82 return res.json(user.toFormatedJSON()) 79 return res.json(user.toFormatedJSON())
@@ -84,7 +81,7 @@ function getUserInformation (req, res, next) {
84} 81}
85 82
86function listUsers (req, res, next) { 83function listUsers (req, res, next) {
87 User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) { 84 db.User.listForApi(req.query.start, req.query.count, req.query.sort, function (err, usersList, usersTotal) {
88 if (err) return next(err) 85 if (err) return next(err)
89 86
90 res.json(getFormatedUsers(usersList, usersTotal)) 87 res.json(getFormatedUsers(usersList, usersTotal))
@@ -94,18 +91,19 @@ function listUsers (req, res, next) {
94function removeUser (req, res, next) { 91function removeUser (req, res, next) {
95 waterfall([ 92 waterfall([
96 function getUser (callback) { 93 function getUser (callback) {
97 User.loadById(req.params.id, callback) 94 db.User.loadById(req.params.id, callback)
98 }, 95 },
99 96
97 // TODO: use foreignkey?
100 function getVideos (user, callback) { 98 function getVideos (user, callback) {
101 Video.listOwnedByAuthor(user.username, function (err, videos) { 99 db.Video.listOwnedByAuthor(user.username, function (err, videos) {
102 return callback(err, user, videos) 100 return callback(err, user, videos)
103 }) 101 })
104 }, 102 },
105 103
106 function removeVideosFromDB (user, videos, callback) { 104 function removeVideosFromDB (user, videos, callback) {
107 each(videos, function (video, callbackEach) { 105 each(videos, function (video, callbackEach) {
108 video.remove(callbackEach) 106 video.destroy().asCallback(callbackEach)
109 }, function (err) { 107 }, function (err) {
110 return callback(err, user, videos) 108 return callback(err, user, videos)
111 }) 109 })
@@ -115,7 +113,7 @@ function removeUser (req, res, next) {
115 videos.forEach(function (video) { 113 videos.forEach(function (video) {
116 const params = { 114 const params = {
117 name: video.name, 115 name: video.name,
118 magnetUri: video.magnetUri 116 remoteId: video.id
119 } 117 }
120 118
121 friends.removeVideoToFriends(params) 119 friends.removeVideoToFriends(params)
@@ -125,7 +123,7 @@ function removeUser (req, res, next) {
125 }, 123 },
126 124
127 function removeUserFromDB (user, callback) { 125 function removeUserFromDB (user, callback) {
128 user.remove(callback) 126 user.destroy().asCallback(callback)
129 } 127 }
130 ], function andFinally (err) { 128 ], function andFinally (err) {
131 if (err) { 129 if (err) {
@@ -138,11 +136,11 @@ function removeUser (req, res, next) {
138} 136}
139 137
140function updateUser (req, res, next) { 138function updateUser (req, res, next) {
141 User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) { 139 db.User.loadByUsername(res.locals.oauth.token.user.username, function (err, user) {
142 if (err) return next(err) 140 if (err) return next(err)
143 141
144 user.password = req.body.password 142 user.password = req.body.password
145 user.save(function (err) { 143 user.save().asCallback(function (err) {
146 if (err) return next(err) 144 if (err) return next(err)
147 145
148 return res.sendStatus(204) 146 return res.sendStatus(204)
diff --git a/server/controllers/api/videos.js b/server/controllers/api/videos.js
index daf452573..a61f2b2c9 100644
--- a/server/controllers/api/videos.js
+++ b/server/controllers/api/videos.js
@@ -2,12 +2,12 @@
2 2
3const express = require('express') 3const express = require('express')
4const fs = require('fs') 4const fs = require('fs')
5const mongoose = require('mongoose')
6const multer = require('multer') 5const multer = require('multer')
7const path = require('path') 6const path = require('path')
8const waterfall = require('async/waterfall') 7const waterfall = require('async/waterfall')
9 8
10const constants = require('../../initializers/constants') 9const constants = require('../../initializers/constants')
10const db = require('../../initializers/database')
11const logger = require('../../helpers/logger') 11const logger = require('../../helpers/logger')
12const friends = require('../../lib/friends') 12const friends = require('../../lib/friends')
13const middlewares = require('../../middlewares') 13const middlewares = require('../../middlewares')
@@ -22,7 +22,6 @@ const sort = middlewares.sort
22const utils = require('../../helpers/utils') 22const utils = require('../../helpers/utils')
23 23
24const router = express.Router() 24const router = express.Router()
25const Video = mongoose.model('Video')
26 25
27// multer configuration 26// multer configuration
28const storage = multer.diskStorage({ 27const storage = multer.diskStorage({
@@ -87,40 +86,60 @@ function addVideo (req, res, next) {
87 const videoInfos = req.body 86 const videoInfos = req.body
88 87
89 waterfall([ 88 waterfall([
90 function createVideoObject (callback) {
91 const id = mongoose.Types.ObjectId()
92 89
90 function findOrCreateAuthor (callback) {
91 const username = res.locals.oauth.token.user.username
92
93 const query = {
94 where: {
95 name: username,
96 podId: null
97 },
98 defaults: {
99 name: username,
100 podId: null // null because it is OUR pod
101 }
102 }
103
104 db.Author.findOrCreate(query).asCallback(function (err, result) {
105 // [ instance, wasCreated ]
106 return callback(err, result[0])
107 })
108 },
109
110 function createVideoObject (author, callback) {
93 const videoData = { 111 const videoData = {
94 _id: id,
95 name: videoInfos.name, 112 name: videoInfos.name,
96 remoteId: null, 113 remoteId: null,
97 extname: path.extname(videoFile.filename), 114 extname: path.extname(videoFile.filename),
98 description: videoInfos.description, 115 description: videoInfos.description,
99 author: res.locals.oauth.token.user.username,
100 duration: videoFile.duration, 116 duration: videoFile.duration,
101 tags: videoInfos.tags 117 tags: videoInfos.tags,
118 authorId: author.id
102 } 119 }
103 120
104 const video = new Video(videoData) 121 const video = db.Video.build(videoData)
105 122
106 return callback(null, video) 123 return callback(null, author, video)
107 }, 124 },
108 125
109 // Set the videoname the same as the MongoDB id 126 // Set the videoname the same as the id
110 function renameVideoFile (video, callback) { 127 function renameVideoFile (author, video, callback) {
111 const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR 128 const videoDir = constants.CONFIG.STORAGE.VIDEOS_DIR
112 const source = path.join(videoDir, videoFile.filename) 129 const source = path.join(videoDir, videoFile.filename)
113 const destination = path.join(videoDir, video.getVideoFilename()) 130 const destination = path.join(videoDir, video.getVideoFilename())
114 131
115 fs.rename(source, destination, function (err) { 132 fs.rename(source, destination, function (err) {
116 return callback(err, video) 133 return callback(err, author, video)
117 }) 134 })
118 }, 135 },
119 136
120 function insertIntoDB (video, callback) { 137 function insertIntoDB (author, video, callback) {
121 video.save(function (err, video) { 138 video.save().asCallback(function (err, videoCreated) {
122 // Assert there are only one argument sent to the next function (video) 139 // Do not forget to add Author informations to the created video
123 return callback(err, video) 140 videoCreated.Author = author
141
142 return callback(err, videoCreated)
124 }) 143 })
125 }, 144 },
126 145
@@ -147,7 +166,7 @@ function addVideo (req, res, next) {
147} 166}
148 167
149function getVideo (req, res, next) { 168function getVideo (req, res, next) {
150 Video.load(req.params.id, function (err, video) { 169 db.Video.loadAndPopulateAuthorAndPod(req.params.id, function (err, video) {
151 if (err) return next(err) 170 if (err) return next(err)
152 171
153 if (!video) { 172 if (!video) {
@@ -159,7 +178,7 @@ function getVideo (req, res, next) {
159} 178}
160 179
161function listVideos (req, res, next) { 180function listVideos (req, res, next) {
162 Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) { 181 db.Video.listForApi(req.query.start, req.query.count, req.query.sort, function (err, videosList, videosTotal) {
163 if (err) return next(err) 182 if (err) return next(err)
164 183
165 res.json(getFormatedVideos(videosList, videosTotal)) 184 res.json(getFormatedVideos(videosList, videosTotal))
@@ -171,11 +190,11 @@ function removeVideo (req, res, next) {
171 190
172 waterfall([ 191 waterfall([
173 function getVideo (callback) { 192 function getVideo (callback) {
174 Video.load(videoId, callback) 193 db.Video.load(videoId, callback)
175 }, 194 },
176 195
177 function removeFromDB (video, callback) { 196 function removeFromDB (video, callback) {
178 video.remove(function (err) { 197 video.destroy().asCallback(function (err) {
179 if (err) return callback(err) 198 if (err) return callback(err)
180 199
181 return callback(null, video) 200 return callback(null, video)
@@ -185,7 +204,7 @@ function removeVideo (req, res, next) {
185 function sendInformationToFriends (video, callback) { 204 function sendInformationToFriends (video, callback) {
186 const params = { 205 const params = {
187 name: video.name, 206 name: video.name,
188 remoteId: video._id 207 remoteId: video.id
189 } 208 }
190 209
191 friends.removeVideoToFriends(params) 210 friends.removeVideoToFriends(params)
@@ -203,7 +222,7 @@ function removeVideo (req, res, next) {
203} 222}
204 223
205function searchVideos (req, res, next) { 224function searchVideos (req, res, next) {
206 Video.search(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort, 225 db.Video.searchAndPopulateAuthorAndPod(req.params.value, req.query.field, req.query.start, req.query.count, req.query.sort,
207 function (err, videosList, videosTotal) { 226 function (err, videosList, videosTotal) {
208 if (err) return next(err) 227 if (err) return next(err)
209 228
diff --git a/server/controllers/client.js b/server/controllers/client.js
index 572db6133..a5fac5626 100644
--- a/server/controllers/client.js
+++ b/server/controllers/client.js
@@ -3,13 +3,12 @@
3const parallel = require('async/parallel') 3const parallel = require('async/parallel')
4const express = require('express') 4const express = require('express')
5const fs = require('fs') 5const fs = require('fs')
6const mongoose = require('mongoose')
7const path = require('path') 6const path = require('path')
8const validator = require('express-validator').validator 7const validator = require('express-validator').validator
9 8
10const constants = require('../initializers/constants') 9const constants = require('../initializers/constants')
10const db = require('../initializers/database')
11 11
12const Video = mongoose.model('Video')
13const router = express.Router() 12const router = express.Router()
14 13
15const opengraphComment = '<!-- opengraph tags -->' 14const opengraphComment = '<!-- opengraph tags -->'
@@ -45,14 +44,14 @@ function addOpenGraphTags (htmlStringPage, video) {
45 if (video.isOwned()) { 44 if (video.isOwned()) {
46 basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL 45 basePreviewUrlHttp = constants.CONFIG.WEBSERVER.URL
47 } else { 46 } else {
48 basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.podHost 47 basePreviewUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + video.Author.Pod.host
49 } 48 }
50 49
51 // We fetch the remote preview (bigger than the thumbnail) 50 // We fetch the remote preview (bigger than the thumbnail)
52 // This should not overhead the remote server since social websites put in a cache the OpenGraph tags 51 // This should not overhead the remote server since social websites put in a cache the OpenGraph tags
53 // We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example) 52 // We can't use the thumbnail because these social websites want bigger images (> 200x200 for Facebook for example)
54 const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName() 53 const previewUrl = basePreviewUrlHttp + constants.STATIC_PATHS.PREVIEWS + video.getPreviewName()
55 const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video._id 54 const videoUrl = constants.CONFIG.WEBSERVER.URL + '/videos/watch/' + video.id
56 55
57 const metaTags = { 56 const metaTags = {
58 'og:type': 'video', 57 'og:type': 'video',
@@ -86,7 +85,7 @@ function generateWatchHtmlPage (req, res, next) {
86 const videoId = req.params.id 85 const videoId = req.params.id
87 86
88 // Let Angular application handle errors 87 // Let Angular application handle errors
89 if (!validator.isMongoId(videoId)) return res.sendFile(indexPath) 88 if (!validator.isUUID(videoId, 4)) return res.sendFile(indexPath)
90 89
91 parallel({ 90 parallel({
92 file: function (callback) { 91 file: function (callback) {
@@ -94,7 +93,7 @@ function generateWatchHtmlPage (req, res, next) {
94 }, 93 },
95 94
96 video: function (callback) { 95 video: function (callback) {
97 Video.load(videoId, callback) 96 db.Video.loadAndPopulateAuthorAndPod(videoId, callback)
98 } 97 }
99 }, function (err, results) { 98 }, function (err, results) {
100 if (err) return next(err) 99 if (err) return next(err)
diff --git a/server/helpers/custom-validators/videos.js b/server/helpers/custom-validators/videos.js
index 1a7753265..be8256a80 100644
--- a/server/helpers/custom-validators/videos.js
+++ b/server/helpers/custom-validators/videos.js
@@ -13,7 +13,7 @@ const videosValidators = {
13 isVideoDateValid, 13 isVideoDateValid,
14 isVideoDescriptionValid, 14 isVideoDescriptionValid,
15 isVideoDurationValid, 15 isVideoDurationValid,
16 isVideoMagnetValid, 16 isVideoInfoHashValid,
17 isVideoNameValid, 17 isVideoNameValid,
18 isVideoPodHostValid, 18 isVideoPodHostValid,
19 isVideoTagsValid, 19 isVideoTagsValid,
@@ -28,14 +28,15 @@ function isEachRemoteVideosValid (requests) {
28 return ( 28 return (
29 isRequestTypeAddValid(request.type) && 29 isRequestTypeAddValid(request.type) &&
30 isVideoAuthorValid(video.author) && 30 isVideoAuthorValid(video.author) &&
31 isVideoDateValid(video.createdDate) && 31 isVideoDateValid(video.createdAt) &&
32 isVideoDescriptionValid(video.description) && 32 isVideoDescriptionValid(video.description) &&
33 isVideoDurationValid(video.duration) && 33 isVideoDurationValid(video.duration) &&
34 isVideoMagnetValid(video.magnet) && 34 isVideoInfoHashValid(video.infoHash) &&
35 isVideoNameValid(video.name) && 35 isVideoNameValid(video.name) &&
36 isVideoTagsValid(video.tags) && 36 isVideoTagsValid(video.tags) &&
37 isVideoThumbnail64Valid(video.thumbnailBase64) && 37 isVideoThumbnail64Valid(video.thumbnailBase64) &&
38 isVideoRemoteIdValid(video.remoteId) 38 isVideoRemoteIdValid(video.remoteId) &&
39 isVideoExtnameValid(video.extname)
39 ) || 40 ) ||
40 ( 41 (
41 isRequestTypeRemoveValid(request.type) && 42 isRequestTypeRemoveValid(request.type) &&
@@ -61,8 +62,12 @@ function isVideoDurationValid (value) {
61 return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION) 62 return validator.isInt(value + '', VIDEOS_CONSTRAINTS_FIELDS.DURATION)
62} 63}
63 64
64function isVideoMagnetValid (value) { 65function isVideoExtnameValid (value) {
65 return validator.isLength(value.infoHash, VIDEOS_CONSTRAINTS_FIELDS.MAGNET.INFO_HASH) 66 return VIDEOS_CONSTRAINTS_FIELDS.EXTNAME.indexOf(value) !== -1
67}
68
69function isVideoInfoHashValid (value) {
70 return validator.isLength(value, VIDEOS_CONSTRAINTS_FIELDS.INFO_HASH)
66} 71}
67 72
68function isVideoNameValid (value) { 73function isVideoNameValid (value) {
@@ -93,7 +98,7 @@ function isVideoThumbnail64Valid (value) {
93} 98}
94 99
95function isVideoRemoteIdValid (value) { 100function isVideoRemoteIdValid (value) {
96 return validator.isMongoId(value) 101 return validator.isUUID(value, 4)
97} 102}
98 103
99// --------------------------------------------------------------------------- 104// ---------------------------------------------------------------------------
diff --git a/server/helpers/logger.js b/server/helpers/logger.js
index fcc1789fd..281acedb8 100644
--- a/server/helpers/logger.js
+++ b/server/helpers/logger.js
@@ -22,7 +22,8 @@ const logger = new winston.Logger({
22 json: true, 22 json: true,
23 maxsize: 5242880, 23 maxsize: 5242880,
24 maxFiles: 5, 24 maxFiles: 5,
25 colorize: false 25 colorize: false,
26 prettyPrint: true
26 }), 27 }),
27 new winston.transports.Console({ 28 new winston.transports.Console({
28 level: 'debug', 29 level: 'debug',
@@ -30,7 +31,8 @@ const logger = new winston.Logger({
30 handleExceptions: true, 31 handleExceptions: true,
31 humanReadableUnhandledException: true, 32 humanReadableUnhandledException: true,
32 json: false, 33 json: false,
33 colorize: true 34 colorize: true,
35 prettyPrint: true
34 }) 36 })
35 ], 37 ],
36 exitOnError: true 38 exitOnError: true
diff --git a/server/initializers/checker.js b/server/initializers/checker.js
index aea013fa9..7b402de82 100644
--- a/server/initializers/checker.js
+++ b/server/initializers/checker.js
@@ -1,10 +1,8 @@
1'use strict' 1'use strict'
2 2
3const config = require('config') 3const config = require('config')
4const mongoose = require('mongoose')
5 4
6const Client = mongoose.model('OAuthClient') 5const db = require('./database')
7const User = mongoose.model('User')
8 6
9const checker = { 7const checker = {
10 checkConfig, 8 checkConfig,
@@ -44,7 +42,7 @@ function checkMissedConfig () {
44} 42}
45 43
46function clientsExist (callback) { 44function clientsExist (callback) {
47 Client.list(function (err, clients) { 45 db.OAuthClient.list(function (err, clients) {
48 if (err) return callback(err) 46 if (err) return callback(err)
49 47
50 return callback(null, clients.length !== 0) 48 return callback(null, clients.length !== 0)
@@ -52,7 +50,7 @@ function clientsExist (callback) {
52} 50}
53 51
54function usersExist (callback) { 52function usersExist (callback) {
55 User.countTotal(function (err, totalUsers) { 53 db.User.countTotal(function (err, totalUsers) {
56 if (err) return callback(err) 54 if (err) return callback(err)
57 55
58 return callback(null, totalUsers !== 0) 56 return callback(null, totalUsers !== 0)
diff --git a/server/initializers/constants.js b/server/initializers/constants.js
index 3ddf87454..1ad0c82a0 100644
--- a/server/initializers/constants.js
+++ b/server/initializers/constants.js
@@ -14,13 +14,13 @@ const PAGINATION_COUNT_DEFAULT = 15
14 14
15// Sortable columns per schema 15// Sortable columns per schema
16const SEARCHABLE_COLUMNS = { 16const SEARCHABLE_COLUMNS = {
17 VIDEOS: [ 'name', 'magnetUri', 'podHost', 'author', 'tags' ] 17 VIDEOS: [ 'name', 'magnetUri', 'host', 'author', 'tags' ]
18} 18}
19 19
20// Sortable columns per schema 20// Sortable columns per schema
21const SORTABLE_COLUMNS = { 21const SORTABLE_COLUMNS = {
22 USERS: [ 'username', '-username', 'createdDate', '-createdDate' ], 22 USERS: [ 'username', '-username', 'createdAt', '-createdAt' ],
23 VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdDate', '-createdDate' ] 23 VIDEOS: [ 'name', '-name', 'duration', '-duration', 'createdAt', '-createdAt' ]
24} 24}
25 25
26const OAUTH_LIFETIME = { 26const OAUTH_LIFETIME = {
@@ -67,9 +67,8 @@ const CONSTRAINTS_FIELDS = {
67 VIDEOS: { 67 VIDEOS: {
68 NAME: { min: 3, max: 50 }, // Length 68 NAME: { min: 3, max: 50 }, // Length
69 DESCRIPTION: { min: 3, max: 250 }, // Length 69 DESCRIPTION: { min: 3, max: 250 }, // Length
70 MAGNET: { 70 EXTNAME: [ '.mp4', '.ogv', '.webm' ],
71 INFO_HASH: { min: 10, max: 50 } // Length 71 INFO_HASH: { min: 10, max: 50 }, // Length
72 },
73 DURATION: { min: 1, max: 7200 }, // Number 72 DURATION: { min: 1, max: 7200 }, // Number
74 TAGS: { min: 1, max: 3 }, // Number of total tags 73 TAGS: { min: 1, max: 3 }, // Number of total tags
75 TAG: { min: 2, max: 10 }, // Length 74 TAG: { min: 2, max: 10 }, // Length
@@ -88,7 +87,7 @@ const FRIEND_SCORE = {
88 87
89// --------------------------------------------------------------------------- 88// ---------------------------------------------------------------------------
90 89
91const MONGO_MIGRATION_SCRIPTS = [ 90const MIGRATION_SCRIPTS = [
92 { 91 {
93 script: '0005-create-application', 92 script: '0005-create-application',
94 version: 5 93 version: 5
@@ -122,7 +121,7 @@ const MONGO_MIGRATION_SCRIPTS = [
122 version: 40 121 version: 40
123 } 122 }
124] 123]
125const LAST_MONGO_SCHEMA_VERSION = (maxBy(MONGO_MIGRATION_SCRIPTS, 'version'))['version'] 124const LAST_SQL_SCHEMA_VERSION = (maxBy(MIGRATION_SCRIPTS, 'version'))['version']
126 125
127// --------------------------------------------------------------------------- 126// ---------------------------------------------------------------------------
128 127
@@ -198,8 +197,8 @@ module.exports = {
198 CONFIG, 197 CONFIG,
199 CONSTRAINTS_FIELDS, 198 CONSTRAINTS_FIELDS,
200 FRIEND_SCORE, 199 FRIEND_SCORE,
201 LAST_MONGO_SCHEMA_VERSION, 200 LAST_SQL_SCHEMA_VERSION,
202 MONGO_MIGRATION_SCRIPTS, 201 MIGRATION_SCRIPTS,
203 OAUTH_LIFETIME, 202 OAUTH_LIFETIME,
204 PAGINATION_COUNT_DEFAULT, 203 PAGINATION_COUNT_DEFAULT,
205 PODS_SCORE, 204 PODS_SCORE,
diff --git a/server/initializers/database.js b/server/initializers/database.js
index 0564e4e77..cc6f59b63 100644
--- a/server/initializers/database.js
+++ b/server/initializers/database.js
@@ -1,36 +1,46 @@
1'use strict' 1'use strict'
2 2
3const mongoose = require('mongoose') 3const fs = require('fs')
4const path = require('path')
5const Sequelize = require('sequelize')
4 6
5const constants = require('../initializers/constants') 7const constants = require('../initializers/constants')
6const logger = require('../helpers/logger') 8const logger = require('../helpers/logger')
7 9
8// Bootstrap models 10const database = {}
9require('../models/application') 11
10require('../models/oauth-token') 12const sequelize = new Sequelize(constants.CONFIG.DATABASE.DBNAME, 'peertube', 'peertube', {
11require('../models/user') 13 dialect: 'postgres',
12require('../models/oauth-client') 14 host: constants.CONFIG.DATABASE.HOSTNAME,
13require('../models/video') 15 port: constants.CONFIG.DATABASE.PORT
14// Request model needs Video model 16})
15require('../models/pods') 17
16// Request model needs Pod model 18const modelDirectory = path.join(__dirname, '..', 'models')
17require('../models/request') 19fs.readdir(modelDirectory, function (err, files) {
18 20 if (err) throw err
19const database = { 21
20 connect: connect 22 files.filter(function (file) {
21} 23 if (file === 'utils.js') return false
22 24
23function connect () { 25 return true
24 mongoose.Promise = global.Promise
25 mongoose.connect('mongodb://' + constants.CONFIG.DATABASE.HOSTNAME + ':' + constants.CONFIG.DATABASE.PORT + '/' + constants.CONFIG.DATABASE.DBNAME)
26 mongoose.connection.on('error', function () {
27 throw new Error('Mongodb connection error.')
28 }) 26 })
27 .forEach(function (file) {
28 const model = sequelize.import(path.join(modelDirectory, file))
29 29
30 mongoose.connection.on('open', function () { 30 database[model.name] = model
31 logger.info('Connected to mongodb.')
32 }) 31 })
33} 32
33 Object.keys(database).forEach(function (modelName) {
34 if ('associate' in database[modelName]) {
35 database[modelName].associate(database)
36 }
37 })
38
39 logger.info('Database is ready.')
40})
41
42database.sequelize = sequelize
43database.Sequelize = Sequelize
34 44
35// --------------------------------------------------------------------------- 45// ---------------------------------------------------------------------------
36 46
diff --git a/server/initializers/installer.js b/server/initializers/installer.js
index 1df300ba8..4823bc8c8 100644
--- a/server/initializers/installer.js
+++ b/server/initializers/installer.js
@@ -3,26 +3,27 @@
3const config = require('config') 3const config = require('config')
4const each = require('async/each') 4const each = require('async/each')
5const mkdirp = require('mkdirp') 5const mkdirp = require('mkdirp')
6const mongoose = require('mongoose')
7const passwordGenerator = require('password-generator') 6const passwordGenerator = require('password-generator')
8const path = require('path') 7const path = require('path')
9const series = require('async/series') 8const series = require('async/series')
10 9
11const checker = require('./checker') 10const checker = require('./checker')
12const constants = require('./constants') 11const constants = require('./constants')
12const db = require('./database')
13const logger = require('../helpers/logger') 13const logger = require('../helpers/logger')
14const peertubeCrypto = require('../helpers/peertube-crypto') 14const peertubeCrypto = require('../helpers/peertube-crypto')
15 15
16const Application = mongoose.model('Application')
17const Client = mongoose.model('OAuthClient')
18const User = mongoose.model('User')
19
20const installer = { 16const installer = {
21 installApplication 17 installApplication
22} 18}
23 19
24function installApplication (callback) { 20function installApplication (callback) {
25 series([ 21 series([
22 function createDatabase (callbackAsync) {
23 db.sequelize.sync().asCallback(callbackAsync)
24 // db.sequelize.sync({ force: true }).asCallback(callbackAsync)
25 },
26
26 function createDirectories (callbackAsync) { 27 function createDirectories (callbackAsync) {
27 createDirectoriesIfNotExist(callbackAsync) 28 createDirectoriesIfNotExist(callbackAsync)
28 }, 29 },
@@ -65,16 +66,18 @@ function createOAuthClientIfNotExist (callback) {
65 66
66 logger.info('Creating a default OAuth Client.') 67 logger.info('Creating a default OAuth Client.')
67 68
68 const secret = passwordGenerator(32, false) 69 const id = passwordGenerator(32, false, /[a-z0-9]/)
69 const client = new Client({ 70 const secret = passwordGenerator(32, false, /[a-zA-Z0-9]/)
71 const client = db.OAuthClient.build({
72 clientId: id,
70 clientSecret: secret, 73 clientSecret: secret,
71 grants: [ 'password', 'refresh_token' ] 74 grants: [ 'password', 'refresh_token' ]
72 }) 75 })
73 76
74 client.save(function (err, createdClient) { 77 client.save().asCallback(function (err, createdClient) {
75 if (err) return callback(err) 78 if (err) return callback(err)
76 79
77 logger.info('Client id: ' + createdClient._id) 80 logger.info('Client id: ' + createdClient.clientId)
78 logger.info('Client secret: ' + createdClient.clientSecret) 81 logger.info('Client secret: ' + createdClient.clientSecret)
79 82
80 return callback(null) 83 return callback(null)
@@ -106,21 +109,21 @@ function createOAuthAdminIfNotExist (callback) {
106 password = passwordGenerator(8, true) 109 password = passwordGenerator(8, true)
107 } 110 }
108 111
109 const user = new User({ 112 const user = db.User.build({
110 username, 113 username,
111 password, 114 password,
112 role 115 role
113 }) 116 })
114 117
115 user.save(function (err, createdUser) { 118 user.save().asCallback(function (err, createdUser) {
116 if (err) return callback(err) 119 if (err) return callback(err)
117 120
118 logger.info('Username: ' + username) 121 logger.info('Username: ' + username)
119 logger.info('User password: ' + password) 122 logger.info('User password: ' + password)
120 123
121 logger.info('Creating Application collection.') 124 logger.info('Creating Application collection.')
122 const application = new Application({ mongoSchemaVersion: constants.LAST_MONGO_SCHEMA_VERSION }) 125 const application = db.Application.build({ sqlSchemaVersion: constants.LAST_SQL_SCHEMA_VERSION })
123 application.save(callback) 126 application.save().asCallback(callback)
124 }) 127 })
125 }) 128 })
126} 129}
diff --git a/server/initializers/migrator.js b/server/initializers/migrator.js
index 6b31d994f..9e5350e60 100644
--- a/server/initializers/migrator.js
+++ b/server/initializers/migrator.js
@@ -1,24 +1,22 @@
1'use strict' 1'use strict'
2 2
3const eachSeries = require('async/eachSeries') 3const eachSeries = require('async/eachSeries')
4const mongoose = require('mongoose')
5const path = require('path') 4const path = require('path')
6 5
7const constants = require('./constants') 6const constants = require('./constants')
7const db = require('./database')
8const logger = require('../helpers/logger') 8const logger = require('../helpers/logger')
9 9
10const Application = mongoose.model('Application')
11
12const migrator = { 10const migrator = {
13 migrate: migrate 11 migrate: migrate
14} 12}
15 13
16function migrate (callback) { 14function migrate (callback) {
17 Application.loadMongoSchemaVersion(function (err, actualVersion) { 15 db.Application.loadSqlSchemaVersion(function (err, actualVersion) {
18 if (err) return callback(err) 16 if (err) return callback(err)
19 17
20 // If there are a new mongo schemas 18 // If there are a new mongo schemas
21 if (!actualVersion || actualVersion < constants.LAST_MONGO_SCHEMA_VERSION) { 19 if (!actualVersion || actualVersion < constants.LAST_SQL_SCHEMA_VERSION) {
22 logger.info('Begin migrations.') 20 logger.info('Begin migrations.')
23 21
24 eachSeries(constants.MONGO_MIGRATION_SCRIPTS, function (entity, callbackEach) { 22 eachSeries(constants.MONGO_MIGRATION_SCRIPTS, function (entity, callbackEach) {
@@ -36,12 +34,12 @@ function migrate (callback) {
36 if (err) return callbackEach(err) 34 if (err) return callbackEach(err)
37 35
38 // Update the new mongo version schema 36 // Update the new mongo version schema
39 Application.updateMongoSchemaVersion(versionScript, callbackEach) 37 db.Application.updateSqlSchemaVersion(versionScript, callbackEach)
40 }) 38 })
41 }, function (err) { 39 }, function (err) {
42 if (err) return callback(err) 40 if (err) return callback(err)
43 41
44 logger.info('Migrations finished. New mongo version schema: %s', constants.LAST_MONGO_SCHEMA_VERSION) 42 logger.info('Migrations finished. New SQL version schema: %s', constants.LAST_SQL_SCHEMA_VERSION)
45 return callback(null) 43 return callback(null)
46 }) 44 })
47 } else { 45 } else {
diff --git a/server/lib/friends.js b/server/lib/friends.js
index eaea040ca..3ed29f651 100644
--- a/server/lib/friends.js
+++ b/server/lib/friends.js
@@ -4,18 +4,14 @@ const each = require('async/each')
4const eachLimit = require('async/eachLimit') 4const eachLimit = require('async/eachLimit')
5const eachSeries = require('async/eachSeries') 5const eachSeries = require('async/eachSeries')
6const fs = require('fs') 6const fs = require('fs')
7const mongoose = require('mongoose')
8const request = require('request') 7const request = require('request')
9const waterfall = require('async/waterfall') 8const waterfall = require('async/waterfall')
10 9
11const constants = require('../initializers/constants') 10const constants = require('../initializers/constants')
11const db = require('../initializers/database')
12const logger = require('../helpers/logger') 12const logger = require('../helpers/logger')
13const requests = require('../helpers/requests') 13const requests = require('../helpers/requests')
14 14
15const Pod = mongoose.model('Pod')
16const Request = mongoose.model('Request')
17const Video = mongoose.model('Video')
18
19const friends = { 15const friends = {
20 addVideoToFriends, 16 addVideoToFriends,
21 hasFriends, 17 hasFriends,
@@ -31,7 +27,7 @@ function addVideoToFriends (video) {
31} 27}
32 28
33function hasFriends (callback) { 29function hasFriends (callback) {
34 Pod.countAll(function (err, count) { 30 db.Pod.countAll(function (err, count) {
35 if (err) return callback(err) 31 if (err) return callback(err)
36 32
37 const hasFriends = (count !== 0) 33 const hasFriends = (count !== 0)
@@ -69,13 +65,13 @@ function makeFriends (hosts, callback) {
69 65
70function quitFriends (callback) { 66function quitFriends (callback) {
71 // Stop pool requests 67 // Stop pool requests
72 Request.deactivate() 68 db.Request.deactivate()
73 // Flush pool requests 69 // Flush pool requests
74 Request.flush() 70 db.Request.flush()
75 71
76 waterfall([ 72 waterfall([
77 function getPodsList (callbackAsync) { 73 function getPodsList (callbackAsync) {
78 return Pod.list(callbackAsync) 74 return db.Pod.list(callbackAsync)
79 }, 75 },
80 76
81 function announceIQuitMyFriends (pods, callbackAsync) { 77 function announceIQuitMyFriends (pods, callbackAsync) {
@@ -103,12 +99,12 @@ function quitFriends (callback) {
103 99
104 function removePodsFromDB (pods, callbackAsync) { 100 function removePodsFromDB (pods, callbackAsync) {
105 each(pods, function (pod, callbackEach) { 101 each(pods, function (pod, callbackEach) {
106 pod.remove(callbackEach) 102 pod.destroy().asCallback(callbackEach)
107 }, callbackAsync) 103 }, callbackAsync)
108 } 104 }
109 ], function (err) { 105 ], function (err) {
110 // Don't forget to re activate the scheduler, even if there was an error 106 // Don't forget to re activate the scheduler, even if there was an error
111 Request.activate() 107 db.Request.activate()
112 108
113 if (err) return callback(err) 109 if (err) return callback(err)
114 110
@@ -122,7 +118,7 @@ function removeVideoToFriends (videoParams) {
122} 118}
123 119
124function sendOwnedVideosToPod (podId) { 120function sendOwnedVideosToPod (podId) {
125 Video.listOwned(function (err, videosList) { 121 db.Video.listOwnedAndPopulateAuthor(function (err, videosList) {
126 if (err) { 122 if (err) {
127 logger.error('Cannot get the list of videos we own.') 123 logger.error('Cannot get the list of videos we own.')
128 return 124 return
@@ -200,9 +196,9 @@ function getForeignPodsList (host, callback) {
200 196
201function makeRequestsToWinningPods (cert, podsList, callback) { 197function makeRequestsToWinningPods (cert, podsList, callback) {
202 // Stop pool requests 198 // Stop pool requests
203 Request.deactivate() 199 db.Request.deactivate()
204 // Flush pool requests 200 // Flush pool requests
205 Request.forceSend() 201 db.Request.forceSend()
206 202
207 eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) { 203 eachLimit(podsList, constants.REQUESTS_IN_PARALLEL, function (pod, callbackEach) {
208 const params = { 204 const params = {
@@ -222,8 +218,8 @@ function makeRequestsToWinningPods (cert, podsList, callback) {
222 } 218 }
223 219
224 if (res.statusCode === 200) { 220 if (res.statusCode === 200) {
225 const podObj = new Pod({ host: pod.host, publicKey: body.cert }) 221 const podObj = db.Pod.build({ host: pod.host, publicKey: body.cert })
226 podObj.save(function (err, podCreated) { 222 podObj.save().asCallback(function (err, podCreated) {
227 if (err) { 223 if (err) {
228 logger.error('Cannot add friend %s pod.', pod.host, { error: err }) 224 logger.error('Cannot add friend %s pod.', pod.host, { error: err })
229 return callbackEach() 225 return callbackEach()
@@ -242,28 +238,57 @@ function makeRequestsToWinningPods (cert, podsList, callback) {
242 }, function endRequests () { 238 }, function endRequests () {
243 // Final callback, we've ended all the requests 239 // Final callback, we've ended all the requests
244 // Now we made new friends, we can re activate the pool of requests 240 // Now we made new friends, we can re activate the pool of requests
245 Request.activate() 241 db.Request.activate()
246 242
247 logger.debug('makeRequestsToWinningPods finished.') 243 logger.debug('makeRequestsToWinningPods finished.')
248 return callback() 244 return callback()
249 }) 245 })
250} 246}
251 247
248// Wrapper that populate "to" argument with all our friends if it is not specified
252function createRequest (type, endpoint, data, to) { 249function createRequest (type, endpoint, data, to) {
253 const req = new Request({ 250 if (to) return _createRequest(type, endpoint, data, to)
251
252 // If the "to" pods is not specified, we send the request to all our friends
253 db.Pod.listAllIds(function (err, podIds) {
254 if (err) {
255 logger.error('Cannot get pod ids', { error: err })
256 return
257 }
258
259 return _createRequest(type, endpoint, data, podIds)
260 })
261}
262
263function _createRequest (type, endpoint, data, to) {
264 const pods = []
265
266 // If there are no destination pods abort
267 if (to.length === 0) return
268
269 to.forEach(function (toPod) {
270 pods.push(db.Pod.build({ id: toPod }))
271 })
272
273 const createQuery = {
254 endpoint, 274 endpoint,
255 request: { 275 request: {
256 type: type, 276 type: type,
257 data: data 277 data: data
258 } 278 }
259 })
260
261 if (to) {
262 req.to = to
263 } 279 }
264 280
265 req.save(function (err) { 281 // We run in transaction to keep coherency between Request and RequestToPod tables
266 if (err) logger.error('Cannot save the request.', { error: err }) 282 db.sequelize.transaction(function (t) {
283 const dbRequestOptions = {
284 transaction: t
285 }
286
287 return db.Request.create(createQuery, dbRequestOptions).then(function (request) {
288 return request.setPods(pods, dbRequestOptions)
289 })
290 }).asCallback(function (err) {
291 if (err) logger.error('Error in createRequest transaction.', { error: err })
267 }) 292 })
268} 293}
269 294
diff --git a/server/lib/oauth-model.js b/server/lib/oauth-model.js
index d011c4b72..1c12f1b14 100644
--- a/server/lib/oauth-model.js
+++ b/server/lib/oauth-model.js
@@ -1,11 +1,6 @@
1const mongoose = require('mongoose') 1const db = require('../initializers/database')
2
3const logger = require('../helpers/logger') 2const logger = require('../helpers/logger')
4 3
5const OAuthClient = mongoose.model('OAuthClient')
6const OAuthToken = mongoose.model('OAuthToken')
7const User = mongoose.model('User')
8
9// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications 4// See https://github.com/oauthjs/node-oauth2-server/wiki/Model-specification for the model specifications
10const OAuthModel = { 5const OAuthModel = {
11 getAccessToken, 6 getAccessToken,
@@ -21,27 +16,25 @@ const OAuthModel = {
21function getAccessToken (bearerToken) { 16function getAccessToken (bearerToken) {
22 logger.debug('Getting access token (bearerToken: ' + bearerToken + ').') 17 logger.debug('Getting access token (bearerToken: ' + bearerToken + ').')
23 18
24 return OAuthToken.getByTokenAndPopulateUser(bearerToken) 19 return db.OAuthToken.getByTokenAndPopulateUser(bearerToken)
25} 20}
26 21
27function getClient (clientId, clientSecret) { 22function getClient (clientId, clientSecret) {
28 logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').') 23 logger.debug('Getting Client (clientId: ' + clientId + ', clientSecret: ' + clientSecret + ').')
29 24
30 // TODO req validator 25 return db.OAuthClient.getByIdAndSecret(clientId, clientSecret)
31 const mongoId = new mongoose.mongo.ObjectID(clientId)
32 return OAuthClient.getByIdAndSecret(mongoId, clientSecret)
33} 26}
34 27
35function getRefreshToken (refreshToken) { 28function getRefreshToken (refreshToken) {
36 logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').') 29 logger.debug('Getting RefreshToken (refreshToken: ' + refreshToken + ').')
37 30
38 return OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken) 31 return db.OAuthToken.getByRefreshTokenAndPopulateClient(refreshToken)
39} 32}
40 33
41function getUser (username, password) { 34function getUser (username, password) {
42 logger.debug('Getting User (username: ' + username + ', password: ' + password + ').') 35 logger.debug('Getting User (username: ' + username + ', password: ' + password + ').')
43 36
44 return User.getByUsername(username).then(function (user) { 37 return db.User.getByUsername(username).then(function (user) {
45 if (!user) return null 38 if (!user) return null
46 39
47 // We need to return a promise 40 // We need to return a promise
@@ -60,8 +53,8 @@ function getUser (username, password) {
60} 53}
61 54
62function revokeToken (token) { 55function revokeToken (token) {
63 return OAuthToken.getByRefreshTokenAndPopulateUser(token.refreshToken).then(function (tokenDB) { 56 return db.OAuthToken.getByRefreshTokenAndPopulateUser(token.refreshToken).then(function (tokenDB) {
64 if (tokenDB) tokenDB.remove() 57 if (tokenDB) tokenDB.destroy()
65 58
66 /* 59 /*
67 * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js 60 * Thanks to https://github.com/manjeshpv/node-oauth2-server-implementation/blob/master/components/oauth/mongo-models.js
@@ -80,18 +73,19 @@ function revokeToken (token) {
80function saveToken (token, client, user) { 73function saveToken (token, client, user) {
81 logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.') 74 logger.debug('Saving token ' + token.accessToken + ' for client ' + client.id + ' and user ' + user.id + '.')
82 75
83 const tokenObj = new OAuthToken({ 76 const tokenToCreate = {
84 accessToken: token.accessToken, 77 accessToken: token.accessToken,
85 accessTokenExpiresAt: token.accessTokenExpiresAt, 78 accessTokenExpiresAt: token.accessTokenExpiresAt,
86 client: client.id,
87 refreshToken: token.refreshToken, 79 refreshToken: token.refreshToken,
88 refreshTokenExpiresAt: token.refreshTokenExpiresAt, 80 refreshTokenExpiresAt: token.refreshTokenExpiresAt,
89 user: user.id 81 oAuthClientId: client.id,
90 }) 82 userId: user.id
83 }
91 84
92 return tokenObj.save().then(function (tokenCreated) { 85 return db.OAuthToken.create(tokenToCreate).then(function (tokenCreated) {
93 tokenCreated.client = client 86 tokenCreated.client = client
94 tokenCreated.user = user 87 tokenCreated.user = user
88
95 return tokenCreated 89 return tokenCreated
96 }).catch(function (err) { 90 }).catch(function (err) {
97 throw err 91 throw err
diff --git a/server/middlewares/pods.js b/server/middlewares/pods.js
index 487ea1259..e38fb341d 100644
--- a/server/middlewares/pods.js
+++ b/server/middlewares/pods.js
@@ -44,7 +44,6 @@ module.exports = podsMiddleware
44function getHostWithPort (host) { 44function getHostWithPort (host) {
45 const splitted = host.split(':') 45 const splitted = host.split(':')
46 46
47 console.log(splitted)
48 // The port was not specified 47 // The port was not specified
49 if (splitted.length === 1) { 48 if (splitted.length === 1) {
50 if (constants.REMOTE_SCHEME.HTTP === 'https') return host + ':443' 49 if (constants.REMOTE_SCHEME.HTTP === 'https') return host + ':443'
diff --git a/server/middlewares/secure.js b/server/middlewares/secure.js
index ee836beed..b7b4cdfb4 100644
--- a/server/middlewares/secure.js
+++ b/server/middlewares/secure.js
@@ -1,18 +1,16 @@
1'use strict' 1'use strict'
2 2
3const db = require('../initializers/database')
3const logger = require('../helpers/logger') 4const logger = require('../helpers/logger')
4const mongoose = require('mongoose')
5const peertubeCrypto = require('../helpers/peertube-crypto') 5const peertubeCrypto = require('../helpers/peertube-crypto')
6 6
7const Pod = mongoose.model('Pod')
8
9const secureMiddleware = { 7const secureMiddleware = {
10 checkSignature 8 checkSignature
11} 9}
12 10
13function checkSignature (req, res, next) { 11function checkSignature (req, res, next) {
14 const host = req.body.signature.host 12 const host = req.body.signature.host
15 Pod.loadByHost(host, function (err, pod) { 13 db.Pod.loadByHost(host, function (err, pod) {
16 if (err) { 14 if (err) {
17 logger.error('Cannot get signed host in body.', { error: err }) 15 logger.error('Cannot get signed host in body.', { error: err })
18 return res.sendStatus(500) 16 return res.sendStatus(500)
diff --git a/server/middlewares/sort.js b/server/middlewares/sort.js
index f0b7274eb..477e10571 100644
--- a/server/middlewares/sort.js
+++ b/server/middlewares/sort.js
@@ -6,13 +6,13 @@ const sortMiddleware = {
6} 6}
7 7
8function setUsersSort (req, res, next) { 8function setUsersSort (req, res, next) {
9 if (!req.query.sort) req.query.sort = '-createdDate' 9 if (!req.query.sort) req.query.sort = '-createdAt'
10 10
11 return next() 11 return next()
12} 12}
13 13
14function setVideosSort (req, res, next) { 14function setVideosSort (req, res, next) {
15 if (!req.query.sort) req.query.sort = '-createdDate' 15 if (!req.query.sort) req.query.sort = '-createdAt'
16 16
17 return next() 17 return next()
18} 18}
diff --git a/server/middlewares/validators/users.js b/server/middlewares/validators/users.js
index 02e4f34cb..0629550bc 100644
--- a/server/middlewares/validators/users.js
+++ b/server/middlewares/validators/users.js
@@ -1,12 +1,9 @@
1'use strict' 1'use strict'
2 2
3const mongoose = require('mongoose')
4
5const checkErrors = require('./utils').checkErrors 3const checkErrors = require('./utils').checkErrors
4const db = require('../../initializers/database')
6const logger = require('../../helpers/logger') 5const logger = require('../../helpers/logger')
7 6
8const User = mongoose.model('User')
9
10const validatorsUsers = { 7const validatorsUsers = {
11 usersAdd, 8 usersAdd,
12 usersRemove, 9 usersRemove,
@@ -20,7 +17,7 @@ function usersAdd (req, res, next) {
20 logger.debug('Checking usersAdd parameters', { parameters: req.body }) 17 logger.debug('Checking usersAdd parameters', { parameters: req.body })
21 18
22 checkErrors(req, res, function () { 19 checkErrors(req, res, function () {
23 User.loadByUsername(req.body.username, function (err, user) { 20 db.User.loadByUsername(req.body.username, function (err, user) {
24 if (err) { 21 if (err) {
25 logger.error('Error in usersAdd request validator.', { error: err }) 22 logger.error('Error in usersAdd request validator.', { error: err })
26 return res.sendStatus(500) 23 return res.sendStatus(500)
@@ -34,12 +31,12 @@ function usersAdd (req, res, next) {
34} 31}
35 32
36function usersRemove (req, res, next) { 33function usersRemove (req, res, next) {
37 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() 34 req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
38 35
39 logger.debug('Checking usersRemove parameters', { parameters: req.params }) 36 logger.debug('Checking usersRemove parameters', { parameters: req.params })
40 37
41 checkErrors(req, res, function () { 38 checkErrors(req, res, function () {
42 User.loadById(req.params.id, function (err, user) { 39 db.User.loadById(req.params.id, function (err, user) {
43 if (err) { 40 if (err) {
44 logger.error('Error in usersRemove request validator.', { error: err }) 41 logger.error('Error in usersRemove request validator.', { error: err })
45 return res.sendStatus(500) 42 return res.sendStatus(500)
@@ -55,7 +52,7 @@ function usersRemove (req, res, next) {
55} 52}
56 53
57function usersUpdate (req, res, next) { 54function usersUpdate (req, res, next) {
58 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() 55 req.checkParams('id', 'Should have a valid id').notEmpty().isInt()
59 // Add old password verification 56 // Add old password verification
60 req.checkBody('password', 'Should have a valid password').isUserPasswordValid() 57 req.checkBody('password', 'Should have a valid password').isUserPasswordValid()
61 58
diff --git a/server/middlewares/validators/videos.js b/server/middlewares/validators/videos.js
index 76e943e77..7e90ca047 100644
--- a/server/middlewares/validators/videos.js
+++ b/server/middlewares/validators/videos.js
@@ -1,14 +1,11 @@
1'use strict' 1'use strict'
2 2
3const mongoose = require('mongoose')
4
5const checkErrors = require('./utils').checkErrors 3const checkErrors = require('./utils').checkErrors
6const constants = require('../../initializers/constants') 4const constants = require('../../initializers/constants')
7const customVideosValidators = require('../../helpers/custom-validators').videos 5const customVideosValidators = require('../../helpers/custom-validators').videos
6const db = require('../../initializers/database')
8const logger = require('../../helpers/logger') 7const logger = require('../../helpers/logger')
9 8
10const Video = mongoose.model('Video')
11
12const validatorsVideos = { 9const validatorsVideos = {
13 videosAdd, 10 videosAdd,
14 videosGet, 11 videosGet,
@@ -29,7 +26,7 @@ function videosAdd (req, res, next) {
29 checkErrors(req, res, function () { 26 checkErrors(req, res, function () {
30 const videoFile = req.files.videofile[0] 27 const videoFile = req.files.videofile[0]
31 28
32 Video.getDurationFromFile(videoFile.path, function (err, duration) { 29 db.Video.getDurationFromFile(videoFile.path, function (err, duration) {
33 if (err) { 30 if (err) {
34 return res.status(400).send('Cannot retrieve metadata of the file.') 31 return res.status(400).send('Cannot retrieve metadata of the file.')
35 } 32 }
@@ -45,12 +42,12 @@ function videosAdd (req, res, next) {
45} 42}
46 43
47function videosGet (req, res, next) { 44function videosGet (req, res, next) {
48 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() 45 req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
49 46
50 logger.debug('Checking videosGet parameters', { parameters: req.params }) 47 logger.debug('Checking videosGet parameters', { parameters: req.params })
51 48
52 checkErrors(req, res, function () { 49 checkErrors(req, res, function () {
53 Video.load(req.params.id, function (err, video) { 50 db.Video.load(req.params.id, function (err, video) {
54 if (err) { 51 if (err) {
55 logger.error('Error in videosGet request validator.', { error: err }) 52 logger.error('Error in videosGet request validator.', { error: err })
56 return res.sendStatus(500) 53 return res.sendStatus(500)
@@ -64,12 +61,12 @@ function videosGet (req, res, next) {
64} 61}
65 62
66function videosRemove (req, res, next) { 63function videosRemove (req, res, next) {
67 req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId() 64 req.checkParams('id', 'Should have a valid id').notEmpty().isUUID(4)
68 65
69 logger.debug('Checking videosRemove parameters', { parameters: req.params }) 66 logger.debug('Checking videosRemove parameters', { parameters: req.params })
70 67
71 checkErrors(req, res, function () { 68 checkErrors(req, res, function () {
72 Video.load(req.params.id, function (err, video) { 69 db.Video.loadAndPopulateAuthor(req.params.id, function (err, video) {
73 if (err) { 70 if (err) {
74 logger.error('Error in videosRemove request validator.', { error: err }) 71 logger.error('Error in videosRemove request validator.', { error: err })
75 return res.sendStatus(500) 72 return res.sendStatus(500)
@@ -77,7 +74,7 @@ function videosRemove (req, res, next) {
77 74
78 if (!video) return res.status(404).send('Video not found') 75 if (!video) return res.status(404).send('Video not found')
79 else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod') 76 else if (video.isOwned() === false) return res.status(403).send('Cannot remove video of another pod')
80 else if (video.author !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user') 77 else if (video.Author.name !== res.locals.oauth.token.user.username) return res.status(403).send('Cannot remove video of another user')
81 78
82 next() 79 next()
83 }) 80 })
diff --git a/server/models/application.js b/server/models/application.js
index 452ac4283..ec1d7b122 100644
--- a/server/models/application.js
+++ b/server/models/application.js
@@ -1,31 +1,36 @@
1const mongoose = require('mongoose') 1module.exports = function (sequelize, DataTypes) {
2 const Application = sequelize.define('Application',
3 {
4 sqlSchemaVersion: {
5 type: DataTypes.INTEGER,
6 defaultValue: 0
7 }
8 },
9 {
10 classMethods: {
11 loadSqlSchemaVersion,
12 updateSqlSchemaVersion
13 }
14 }
15 )
16
17 return Application
18}
2 19
3// --------------------------------------------------------------------------- 20// ---------------------------------------------------------------------------
4 21
5const ApplicationSchema = mongoose.Schema({ 22function loadSqlSchemaVersion (callback) {
6 mongoSchemaVersion: { 23 const query = {
7 type: Number, 24 attributes: [ 'sqlSchemaVersion' ]
8 default: 0
9 } 25 }
10})
11
12ApplicationSchema.statics = {
13 loadMongoSchemaVersion,
14 updateMongoSchemaVersion
15}
16
17mongoose.model('Application', ApplicationSchema)
18
19// ---------------------------------------------------------------------------
20 26
21function loadMongoSchemaVersion (callback) { 27 return this.findOne(query).asCallback(function (err, data) {
22 return this.findOne({}, { mongoSchemaVersion: 1 }, function (err, data) { 28 const version = data ? data.sqlSchemaVersion : 0
23 const version = data ? data.mongoSchemaVersion : 0
24 29
25 return callback(err, version) 30 return callback(err, version)
26 }) 31 })
27} 32}
28 33
29function updateMongoSchemaVersion (newVersion, callback) { 34function updateSqlSchemaVersion (newVersion, callback) {
30 return this.update({}, { mongoSchemaVersion: newVersion }, callback) 35 return this.update({ sqlSchemaVersion: newVersion }).asCallback(callback)
31} 36}
diff --git a/server/models/author.js b/server/models/author.js
new file mode 100644
index 000000000..493c2ca11
--- /dev/null
+++ b/server/models/author.js
@@ -0,0 +1,28 @@
1module.exports = function (sequelize, DataTypes) {
2 const Author = sequelize.define('Author',
3 {
4 name: {
5 type: DataTypes.STRING
6 }
7 },
8 {
9 classMethods: {
10 associate
11 }
12 }
13 )
14
15 return Author
16}
17
18// ---------------------------------------------------------------------------
19
20function associate (models) {
21 this.belongsTo(models.Pod, {
22 foreignKey: {
23 name: 'podId',
24 allowNull: true
25 },
26 onDelete: 'cascade'
27 })
28}
diff --git a/server/models/oauth-client.js b/server/models/oauth-client.js
index a1aefa985..15118591a 100644
--- a/server/models/oauth-client.js
+++ b/server/models/oauth-client.js
@@ -1,33 +1,63 @@
1const mongoose = require('mongoose') 1module.exports = function (sequelize, DataTypes) {
2 2 const OAuthClient = sequelize.define('OAuthClient',
3// --------------------------------------------------------------------------- 3 {
4 4 clientId: {
5const OAuthClientSchema = mongoose.Schema({ 5 type: DataTypes.STRING
6 clientSecret: String, 6 },
7 grants: Array, 7 clientSecret: {
8 redirectUris: Array 8 type: DataTypes.STRING
9}) 9 },
10 10 grants: {
11OAuthClientSchema.path('clientSecret').required(true) 11 type: DataTypes.ARRAY(DataTypes.STRING)
12 12 },
13OAuthClientSchema.statics = { 13 redirectUris: {
14 getByIdAndSecret, 14 type: DataTypes.ARRAY(DataTypes.STRING)
15 list, 15 }
16 loadFirstClient 16 },
17 {
18 classMethods: {
19 associate,
20
21 getByIdAndSecret,
22 list,
23 loadFirstClient
24 }
25 }
26 )
27
28 return OAuthClient
17} 29}
18 30
19mongoose.model('OAuthClient', OAuthClientSchema) 31// TODO: validation
32// OAuthClientSchema.path('clientSecret').required(true)
20 33
21// --------------------------------------------------------------------------- 34// ---------------------------------------------------------------------------
22 35
36function associate (models) {
37 this.hasMany(models.OAuthToken, {
38 foreignKey: {
39 name: 'oAuthClientId',
40 allowNull: false
41 },
42 onDelete: 'cascade'
43 })
44}
45
23function list (callback) { 46function list (callback) {
24 return this.find(callback) 47 return this.findAll().asCallback(callback)
25} 48}
26 49
27function loadFirstClient (callback) { 50function loadFirstClient (callback) {
28 return this.findOne({}, callback) 51 return this.findOne().asCallback(callback)
29} 52}
30 53
31function getByIdAndSecret (id, clientSecret) { 54function getByIdAndSecret (clientId, clientSecret) {
32 return this.findOne({ _id: id, clientSecret: clientSecret }).exec() 55 const query = {
56 where: {
57 clientId: clientId,
58 clientSecret: clientSecret
59 }
60 }
61
62 return this.findOne(query)
33} 63}
diff --git a/server/models/oauth-token.js b/server/models/oauth-token.js
index aff73bfb1..c9108bf95 100644
--- a/server/models/oauth-token.js
+++ b/server/models/oauth-token.js
@@ -1,42 +1,71 @@
1const mongoose = require('mongoose')
2
3const logger = require('../helpers/logger') 1const logger = require('../helpers/logger')
4 2
5// --------------------------------------------------------------------------- 3// ---------------------------------------------------------------------------
6 4
7const OAuthTokenSchema = mongoose.Schema({ 5module.exports = function (sequelize, DataTypes) {
8 accessToken: String, 6 const OAuthToken = sequelize.define('OAuthToken',
9 accessTokenExpiresAt: Date, 7 {
10 client: { type: mongoose.Schema.Types.ObjectId, ref: 'OAuthClient' }, 8 accessToken: {
11 refreshToken: String, 9 type: DataTypes.STRING
12 refreshTokenExpiresAt: Date, 10 },
13 user: { type: mongoose.Schema.Types.ObjectId, ref: 'User' } 11 accessTokenExpiresAt: {
14}) 12 type: DataTypes.DATE
15 13 },
16OAuthTokenSchema.path('accessToken').required(true) 14 refreshToken: {
17OAuthTokenSchema.path('client').required(true) 15 type: DataTypes.STRING
18OAuthTokenSchema.path('user').required(true) 16 },
19 17 refreshTokenExpiresAt: {
20OAuthTokenSchema.statics = { 18 type: DataTypes.DATE
21 getByRefreshTokenAndPopulateClient, 19 }
22 getByTokenAndPopulateUser, 20 },
23 getByRefreshTokenAndPopulateUser, 21 {
24 removeByUserId 22 classMethods: {
23 associate,
24
25 getByRefreshTokenAndPopulateClient,
26 getByTokenAndPopulateUser,
27 getByRefreshTokenAndPopulateUser,
28 removeByUserId
29 }
30 }
31 )
32
33 return OAuthToken
25} 34}
26 35
27mongoose.model('OAuthToken', OAuthTokenSchema) 36// TODO: validation
37// OAuthTokenSchema.path('accessToken').required(true)
38// OAuthTokenSchema.path('client').required(true)
39// OAuthTokenSchema.path('user').required(true)
28 40
29// --------------------------------------------------------------------------- 41// ---------------------------------------------------------------------------
30 42
43function associate (models) {
44 this.belongsTo(models.User, {
45 foreignKey: {
46 name: 'userId',
47 allowNull: false
48 },
49 onDelete: 'cascade'
50 })
51}
52
31function getByRefreshTokenAndPopulateClient (refreshToken) { 53function getByRefreshTokenAndPopulateClient (refreshToken) {
32 return this.findOne({ refreshToken: refreshToken }).populate('client').exec().then(function (token) { 54 const query = {
55 where: {
56 refreshToken: refreshToken
57 },
58 include: [ this.associations.OAuthClient ]
59 }
60
61 return this.findOne(query).then(function (token) {
33 if (!token) return token 62 if (!token) return token
34 63
35 const tokenInfos = { 64 const tokenInfos = {
36 refreshToken: token.refreshToken, 65 refreshToken: token.refreshToken,
37 refreshTokenExpiresAt: token.refreshTokenExpiresAt, 66 refreshTokenExpiresAt: token.refreshTokenExpiresAt,
38 client: { 67 client: {
39 id: token.client._id.toString() 68 id: token.client.id
40 }, 69 },
41 user: { 70 user: {
42 id: token.user 71 id: token.user
@@ -50,13 +79,41 @@ function getByRefreshTokenAndPopulateClient (refreshToken) {
50} 79}
51 80
52function getByTokenAndPopulateUser (bearerToken) { 81function getByTokenAndPopulateUser (bearerToken) {
53 return this.findOne({ accessToken: bearerToken }).populate('user').exec() 82 const query = {
83 where: {
84 accessToken: bearerToken
85 },
86 include: [ this.sequelize.models.User ]
87 }
88
89 return this.findOne(query).then(function (token) {
90 if (token) token.user = token.User
91
92 return token
93 })
54} 94}
55 95
56function getByRefreshTokenAndPopulateUser (refreshToken) { 96function getByRefreshTokenAndPopulateUser (refreshToken) {
57 return this.findOne({ refreshToken: refreshToken }).populate('user').exec() 97 const query = {
98 where: {
99 refreshToken: refreshToken
100 },
101 include: [ this.sequelize.models.User ]
102 }
103
104 return this.findOne(query).then(function (token) {
105 token.user = token.User
106
107 return token
108 })
58} 109}
59 110
60function removeByUserId (userId, callback) { 111function removeByUserId (userId, callback) {
61 return this.remove({ user: userId }, callback) 112 const query = {
113 where: {
114 userId: userId
115 }
116 }
117
118 return this.destroy(query).asCallback(callback)
62} 119}
diff --git a/server/models/pods.js b/server/models/pods.js
index 49c73472a..2c1f56203 100644
--- a/server/models/pods.js
+++ b/server/models/pods.js
@@ -1,79 +1,62 @@
1'use strict' 1'use strict'
2 2
3const each = require('async/each')
4const mongoose = require('mongoose')
5const map = require('lodash/map') 3const map = require('lodash/map')
6const validator = require('express-validator').validator
7 4
8const constants = require('../initializers/constants') 5const constants = require('../initializers/constants')
9 6
10const Video = mongoose.model('Video')
11
12// --------------------------------------------------------------------------- 7// ---------------------------------------------------------------------------
13 8
14const PodSchema = mongoose.Schema({ 9module.exports = function (sequelize, DataTypes) {
15 host: String, 10 const Pod = sequelize.define('Pod',
16 publicKey: String, 11 {
17 score: { type: Number, max: constants.FRIEND_SCORE.MAX }, 12 host: {
18 createdDate: { 13 type: DataTypes.STRING
19 type: Date, 14 },
20 default: Date.now 15 publicKey: {
21 } 16 type: DataTypes.STRING(5000)
22}) 17 },
23 18 score: {
24PodSchema.path('host').validate(validator.isURL) 19 type: DataTypes.INTEGER,
25PodSchema.path('publicKey').required(true) 20 defaultValue: constants.FRIEND_SCORE.BASE
26PodSchema.path('score').validate(function (value) { return !isNaN(value) }) 21 }
27 22 // Check createdAt
28PodSchema.methods = { 23 },
29 toFormatedJSON 24 {
25 classMethods: {
26 associate,
27
28 countAll,
29 incrementScores,
30 list,
31 listAllIds,
32 listBadPods,
33 load,
34 loadByHost,
35 removeAll
36 },
37 instanceMethods: {
38 toFormatedJSON
39 }
40 }
41 )
42
43 return Pod
30} 44}
31 45
32PodSchema.statics = { 46// TODO: max score -> constants.FRIENDS_SCORE.MAX
33 countAll, 47// TODO: validation
34 incrementScores, 48// PodSchema.path('host').validate(validator.isURL)
35 list, 49// PodSchema.path('publicKey').required(true)
36 listAllIds, 50// PodSchema.path('score').validate(function (value) { return !isNaN(value) })
37 listBadPods,
38 load,
39 loadByHost,
40 removeAll
41}
42
43PodSchema.pre('save', function (next) {
44 const self = this
45
46 Pod.loadByHost(this.host, function (err, pod) {
47 if (err) return next(err)
48
49 if (pod) return next(new Error('Pod already exists.'))
50
51 self.score = constants.FRIEND_SCORE.BASE
52 return next()
53 })
54})
55
56PodSchema.pre('remove', function (next) {
57 // Remove the videos owned by this pod too
58 Video.listByHost(this.host, function (err, videos) {
59 if (err) return next(err)
60
61 each(videos, function (video, callbackEach) {
62 video.remove(callbackEach)
63 }, next)
64 })
65})
66
67const Pod = mongoose.model('Pod', PodSchema)
68 51
69// ------------------------------ METHODS ------------------------------ 52// ------------------------------ METHODS ------------------------------
70 53
71function toFormatedJSON () { 54function toFormatedJSON () {
72 const json = { 55 const json = {
73 id: this._id, 56 id: this.id,
74 host: this.host, 57 host: this.host,
75 score: this.score, 58 score: this.score,
76 createdDate: this.createdDate 59 createdAt: this.createdAt
77 } 60 }
78 61
79 return json 62 return json
@@ -81,39 +64,76 @@ function toFormatedJSON () {
81 64
82// ------------------------------ Statics ------------------------------ 65// ------------------------------ Statics ------------------------------
83 66
67function associate (models) {
68 this.belongsToMany(models.Request, {
69 foreignKey: 'podId',
70 through: models.RequestToPod,
71 onDelete: 'CASCADE'
72 })
73}
74
84function countAll (callback) { 75function countAll (callback) {
85 return this.count(callback) 76 return this.count().asCallback(callback)
86} 77}
87 78
88function incrementScores (ids, value, callback) { 79function incrementScores (ids, value, callback) {
89 if (!callback) callback = function () {} 80 if (!callback) callback = function () {}
90 return this.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback) 81
82 const update = {
83 score: this.sequelize.literal('score +' + value)
84 }
85
86 const query = {
87 where: {
88 id: {
89 $in: ids
90 }
91 }
92 }
93
94 return this.update(update, query).asCallback(callback)
91} 95}
92 96
93function list (callback) { 97function list (callback) {
94 return this.find(callback) 98 return this.findAll().asCallback(callback)
95} 99}
96 100
97function listAllIds (callback) { 101function listAllIds (callback) {
98 return this.find({}, { _id: 1 }, function (err, pods) { 102 const query = {
103 attributes: [ 'id' ]
104 }
105
106 return this.findAll(query).asCallback(function (err, pods) {
99 if (err) return callback(err) 107 if (err) return callback(err)
100 108
101 return callback(null, map(pods, '_id')) 109 return callback(null, map(pods, 'id'))
102 }) 110 })
103} 111}
104 112
105function listBadPods (callback) { 113function listBadPods (callback) {
106 return this.find({ score: 0 }, callback) 114 const query = {
115 where: {
116 score: { $lte: 0 }
117 }
118 }
119
120 return this.findAll(query).asCallback(callback)
107} 121}
108 122
109function load (id, callback) { 123function load (id, callback) {
110 return this.findById(id, callback) 124 return this.findById(id).asCallback(callback)
111} 125}
112 126
113function loadByHost (host, callback) { 127function loadByHost (host, callback) {
114 return this.findOne({ host }, callback) 128 const query = {
129 where: {
130 host: host
131 }
132 }
133
134 return this.findOne(query).asCallback(callback)
115} 135}
116 136
117function removeAll (callback) { 137function removeAll (callback) {
118 return this.remove({}, callback) 138 return this.destroy().asCallback(callback)
119} 139}
diff --git a/server/models/request.js b/server/models/request.js
index c2cfe83ce..882f747b7 100644
--- a/server/models/request.js
+++ b/server/models/request.js
@@ -2,66 +2,58 @@
2 2
3const each = require('async/each') 3const each = require('async/each')
4const eachLimit = require('async/eachLimit') 4const eachLimit = require('async/eachLimit')
5const values = require('lodash/values')
6const mongoose = require('mongoose')
7const waterfall = require('async/waterfall') 5const waterfall = require('async/waterfall')
8 6
9const constants = require('../initializers/constants') 7const constants = require('../initializers/constants')
10const logger = require('../helpers/logger') 8const logger = require('../helpers/logger')
11const requests = require('../helpers/requests') 9const requests = require('../helpers/requests')
12 10
13const Pod = mongoose.model('Pod')
14
15let timer = null 11let timer = null
16let lastRequestTimestamp = 0 12let lastRequestTimestamp = 0
17 13
18// --------------------------------------------------------------------------- 14// ---------------------------------------------------------------------------
19 15
20const RequestSchema = mongoose.Schema({ 16module.exports = function (sequelize, DataTypes) {
21 request: mongoose.Schema.Types.Mixed, 17 const Request = sequelize.define('Request',
22 endpoint: { 18 {
23 type: String, 19 request: {
24 enum: [ values(constants.REQUEST_ENDPOINTS) ] 20 type: DataTypes.JSON
25 }, 21 },
26 to: [ 22 endpoint: {
23 // TODO: enum?
24 type: DataTypes.STRING
25 }
26 },
27 { 27 {
28 type: mongoose.Schema.Types.ObjectId, 28 classMethods: {
29 ref: 'Pod' 29 associate,
30
31 activate,
32 countTotalRequests,
33 deactivate,
34 flush,
35 forceSend,
36 remainingMilliSeconds
37 }
30 } 38 }
31 ] 39 )
32})
33
34RequestSchema.statics = {
35 activate,
36 deactivate,
37 flush,
38 forceSend,
39 list,
40 remainingMilliSeconds
41}
42
43RequestSchema.pre('save', function (next) {
44 const self = this
45
46 if (self.to.length === 0) {
47 Pod.listAllIds(function (err, podIds) {
48 if (err) return next(err)
49
50 // No friends
51 if (podIds.length === 0) return
52
53 self.to = podIds
54 return next()
55 })
56 } else {
57 return next()
58 }
59})
60 40
61mongoose.model('Request', RequestSchema) 41 return Request
42}
62 43
63// ------------------------------ STATICS ------------------------------ 44// ------------------------------ STATICS ------------------------------
64 45
46function associate (models) {
47 this.belongsToMany(models.Pod, {
48 foreignKey: {
49 name: 'requestId',
50 allowNull: false
51 },
52 through: models.RequestToPod,
53 onDelete: 'CASCADE'
54 })
55}
56
65function activate () { 57function activate () {
66 logger.info('Requests scheduler activated.') 58 logger.info('Requests scheduler activated.')
67 lastRequestTimestamp = Date.now() 59 lastRequestTimestamp = Date.now()
@@ -73,6 +65,14 @@ function activate () {
73 }, constants.REQUESTS_INTERVAL) 65 }, constants.REQUESTS_INTERVAL)
74} 66}
75 67
68function countTotalRequests (callback) {
69 const query = {
70 include: [ this.sequelize.models.Pod ]
71 }
72
73 return this.count(query).asCallback(callback)
74}
75
76function deactivate () { 76function deactivate () {
77 logger.info('Requests scheduler deactivated.') 77 logger.info('Requests scheduler deactivated.')
78 clearInterval(timer) 78 clearInterval(timer)
@@ -90,10 +90,6 @@ function forceSend () {
90 makeRequests.call(this) 90 makeRequests.call(this)
91} 91}
92 92
93function list (callback) {
94 this.find({ }, callback)
95}
96
97function remainingMilliSeconds () { 93function remainingMilliSeconds () {
98 if (timer === null) return -1 94 if (timer === null) return -1
99 95
@@ -136,6 +132,7 @@ function makeRequest (toPod, requestEndpoint, requestsToMake, callback) {
136// Make all the requests of the scheduler 132// Make all the requests of the scheduler
137function makeRequests () { 133function makeRequests () {
138 const self = this 134 const self = this
135 const RequestToPod = this.sequelize.models.RequestToPod
139 136
140 // We limit the size of the requests (REQUESTS_LIMIT) 137 // We limit the size of the requests (REQUESTS_LIMIT)
141 // We don't want to stuck with the same failing requests so we get a random list 138 // We don't want to stuck with the same failing requests so we get a random list
@@ -156,20 +153,20 @@ function makeRequests () {
156 // We want to group requests by destinations pod and endpoint 153 // We want to group requests by destinations pod and endpoint
157 const requestsToMakeGrouped = {} 154 const requestsToMakeGrouped = {}
158 155
159 requests.forEach(function (poolRequest) { 156 requests.forEach(function (request) {
160 poolRequest.to.forEach(function (toPodId) { 157 request.Pods.forEach(function (toPod) {
161 const hashKey = toPodId + poolRequest.endpoint 158 const hashKey = toPod.id + request.endpoint
162 if (!requestsToMakeGrouped[hashKey]) { 159 if (!requestsToMakeGrouped[hashKey]) {
163 requestsToMakeGrouped[hashKey] = { 160 requestsToMakeGrouped[hashKey] = {
164 toPodId, 161 toPodId: toPod.id,
165 endpoint: poolRequest.endpoint, 162 endpoint: request.endpoint,
166 ids: [], // pool request ids, to delete them from the DB in the future 163 ids: [], // request ids, to delete them from the DB in the future
167 datas: [] // requests data, 164 datas: [] // requests data,
168 } 165 }
169 } 166 }
170 167
171 requestsToMakeGrouped[hashKey].ids.push(poolRequest._id) 168 requestsToMakeGrouped[hashKey].ids.push(request.id)
172 requestsToMakeGrouped[hashKey].datas.push(poolRequest.request) 169 requestsToMakeGrouped[hashKey].datas.push(request.request)
173 }) 170 })
174 }) 171 })
175 172
@@ -179,8 +176,8 @@ function makeRequests () {
179 eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) { 176 eachLimit(Object.keys(requestsToMakeGrouped), constants.REQUESTS_IN_PARALLEL, function (hashKey, callbackEach) {
180 const requestToMake = requestsToMakeGrouped[hashKey] 177 const requestToMake = requestsToMakeGrouped[hashKey]
181 178
182 // FIXME: mongodb request inside a loop :/ 179 // FIXME: SQL request inside a loop :/
183 Pod.load(requestToMake.toPodId, function (err, toPod) { 180 self.sequelize.models.Pod.load(requestToMake.toPodId, function (err, toPod) {
184 if (err) { 181 if (err) {
185 logger.error('Error finding pod by id.', { err: err }) 182 logger.error('Error finding pod by id.', { err: err })
186 return callbackEach() 183 return callbackEach()
@@ -191,7 +188,7 @@ function makeRequests () {
191 const requestIdsToDelete = requestToMake.ids 188 const requestIdsToDelete = requestToMake.ids
192 189
193 logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPodId) 190 logger.info('Removing %d requests of unexisting pod %s.', requestIdsToDelete.length, requestToMake.toPodId)
194 removePodOf.call(self, requestIdsToDelete, requestToMake.toPodId) 191 RequestToPod.removePodOf.call(self, requestIdsToDelete, requestToMake.toPodId)
195 return callbackEach() 192 return callbackEach()
196 } 193 }
197 194
@@ -202,7 +199,7 @@ function makeRequests () {
202 goodPods.push(requestToMake.toPodId) 199 goodPods.push(requestToMake.toPodId)
203 200
204 // Remove the pod id of these request ids 201 // Remove the pod id of these request ids
205 removePodOf.call(self, requestToMake.ids, requestToMake.toPodId, callbackEach) 202 RequestToPod.removePodOf(requestToMake.ids, requestToMake.toPodId, callbackEach)
206 } else { 203 } else {
207 badPods.push(requestToMake.toPodId) 204 badPods.push(requestToMake.toPodId)
208 callbackEach() 205 callbackEach()
@@ -211,18 +208,22 @@ function makeRequests () {
211 }) 208 })
212 }, function () { 209 }, function () {
213 // All the requests were made, we update the pods score 210 // All the requests were made, we update the pods score
214 updatePodsScore(goodPods, badPods) 211 updatePodsScore.call(self, goodPods, badPods)
215 // Flush requests with no pod 212 // Flush requests with no pod
216 removeWithEmptyTo.call(self) 213 removeWithEmptyTo.call(self, function (err) {
214 if (err) logger.error('Error when removing requests with no pods.', { error: err })
215 })
217 }) 216 })
218 }) 217 })
219} 218}
220 219
221// Remove pods with a score of 0 (too many requests where they were unreachable) 220// Remove pods with a score of 0 (too many requests where they were unreachable)
222function removeBadPods () { 221function removeBadPods () {
222 const self = this
223
223 waterfall([ 224 waterfall([
224 function findBadPods (callback) { 225 function findBadPods (callback) {
225 Pod.listBadPods(function (err, pods) { 226 self.sequelize.models.Pod.listBadPods(function (err, pods) {
226 if (err) { 227 if (err) {
227 logger.error('Cannot find bad pods.', { error: err }) 228 logger.error('Cannot find bad pods.', { error: err })
228 return callback(err) 229 return callback(err)
@@ -233,10 +234,8 @@ function removeBadPods () {
233 }, 234 },
234 235
235 function removeTheseBadPods (pods, callback) { 236 function removeTheseBadPods (pods, callback) {
236 if (pods.length === 0) return callback(null, 0)
237
238 each(pods, function (pod, callbackEach) { 237 each(pods, function (pod, callbackEach) {
239 pod.remove(callbackEach) 238 pod.destroy().asCallback(callbackEach)
240 }, function (err) { 239 }, function (err) {
241 return callback(err, pods.length) 240 return callback(err, pods.length)
242 }) 241 })
@@ -253,43 +252,67 @@ function removeBadPods () {
253} 252}
254 253
255function updatePodsScore (goodPods, badPods) { 254function updatePodsScore (goodPods, badPods) {
255 const self = this
256 const Pod = this.sequelize.models.Pod
257
256 logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length) 258 logger.info('Updating %d good pods and %d bad pods scores.', goodPods.length, badPods.length)
257 259
258 Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) { 260 if (goodPods.length !== 0) {
259 if (err) logger.error('Cannot increment scores of good pods.') 261 Pod.incrementScores(goodPods, constants.PODS_SCORE.BONUS, function (err) {
260 }) 262 if (err) logger.error('Cannot increment scores of good pods.')
263 })
264 }
261 265
262 Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) { 266 if (badPods.length !== 0) {
263 if (err) logger.error('Cannot decrement scores of bad pods.') 267 Pod.incrementScores(badPods, constants.PODS_SCORE.MALUS, function (err) {
264 removeBadPods() 268 if (err) logger.error('Cannot decrement scores of bad pods.')
265 }) 269 removeBadPods.call(self)
270 })
271 }
266} 272}
267 273
268function listWithLimitAndRandom (limit, callback) { 274function listWithLimitAndRandom (limit, callback) {
269 const self = this 275 const self = this
270 276
271 self.count(function (err, count) { 277 self.count().asCallback(function (err, count) {
272 if (err) return callback(err) 278 if (err) return callback(err)
273 279
280 // Optimization...
281 if (count === 0) return callback(null, [])
282
274 let start = Math.floor(Math.random() * count) - limit 283 let start = Math.floor(Math.random() * count) - limit
275 if (start < 0) start = 0 284 if (start < 0) start = 0
276 285
277 self.find().sort({ _id: 1 }).skip(start).limit(limit).exec(callback) 286 const query = {
287 order: [
288 [ 'id', 'ASC' ]
289 ],
290 offset: start,
291 limit: limit,
292 include: [ this.sequelize.models.Pod ]
293 }
294
295 self.findAll(query).asCallback(callback)
278 }) 296 })
279} 297}
280 298
281function removeAll (callback) { 299function removeAll (callback) {
282 this.remove({ }, callback) 300 // Delete all requests
283} 301 this.destroy({ truncate: true }).asCallback(callback)
284
285function removePodOf (requestsIds, podId, callback) {
286 if (!callback) callback = function () {}
287
288 this.update({ _id: { $in: requestsIds } }, { $pull: { to: podId } }, { multi: true }, callback)
289} 302}
290 303
291function removeWithEmptyTo (callback) { 304function removeWithEmptyTo (callback) {
292 if (!callback) callback = function () {} 305 if (!callback) callback = function () {}
293 306
294 this.remove({ to: { $size: 0 } }, callback) 307 const query = {
308 where: {
309 id: {
310 $notIn: [
311 this.sequelize.literal('SELECT "requestId" FROM "RequestToPods"')
312 ]
313 }
314 }
315 }
316
317 this.destroy(query).asCallback(callback)
295} 318}
diff --git a/server/models/requestToPod.js b/server/models/requestToPod.js
new file mode 100644
index 000000000..378c2bdcf
--- /dev/null
+++ b/server/models/requestToPod.js
@@ -0,0 +1,30 @@
1'use strict'
2
3// ---------------------------------------------------------------------------
4
5module.exports = function (sequelize, DataTypes) {
6 const RequestToPod = sequelize.define('RequestToPod', {}, {
7 classMethods: {
8 removePodOf
9 }
10 })
11
12 return RequestToPod
13}
14
15// ---------------------------------------------------------------------------
16
17function removePodOf (requestsIds, podId, callback) {
18 if (!callback) callback = function () {}
19
20 const query = {
21 where: {
22 requestId: {
23 $in: requestsIds
24 },
25 podId: podId
26 }
27 }
28
29 this.destroy(query).asCallback(callback)
30}
diff --git a/server/models/user.js b/server/models/user.js
index a19de7072..e50eb96ea 100644
--- a/server/models/user.js
+++ b/server/models/user.js
@@ -1,60 +1,60 @@
1const mongoose = require('mongoose')
2
3const customUsersValidators = require('../helpers/custom-validators').users
4const modelUtils = require('./utils') 1const modelUtils = require('./utils')
5const peertubeCrypto = require('../helpers/peertube-crypto') 2const peertubeCrypto = require('../helpers/peertube-crypto')
6 3
7const OAuthToken = mongoose.model('OAuthToken')
8
9// --------------------------------------------------------------------------- 4// ---------------------------------------------------------------------------
10 5
11const UserSchema = mongoose.Schema({ 6module.exports = function (sequelize, DataTypes) {
12 createdDate: { 7 const User = sequelize.define('User',
13 type: Date, 8 {
14 default: Date.now 9 password: {
15 }, 10 type: DataTypes.STRING
16 password: String, 11 },
17 username: String, 12 username: {
18 role: String 13 type: DataTypes.STRING
19}) 14 },
20 15 role: {
21UserSchema.path('password').required(customUsersValidators.isUserPasswordValid) 16 type: DataTypes.STRING
22UserSchema.path('username').required(customUsersValidators.isUserUsernameValid) 17 }
23UserSchema.path('role').validate(customUsersValidators.isUserRoleValid) 18 },
24 19 {
25UserSchema.methods = { 20 classMethods: {
26 isPasswordMatch, 21 associate,
27 toFormatedJSON 22
23 countTotal,
24 getByUsername,
25 list,
26 listForApi,
27 loadById,
28 loadByUsername
29 },
30 instanceMethods: {
31 isPasswordMatch,
32 toFormatedJSON
33 },
34 hooks: {
35 beforeCreate: beforeCreateOrUpdate,
36 beforeUpdate: beforeCreateOrUpdate
37 }
38 }
39 )
40
41 return User
28} 42}
29 43
30UserSchema.statics = { 44// TODO: Validation
31 countTotal, 45// UserSchema.path('password').required(customUsersValidators.isUserPasswordValid)
32 getByUsername, 46// UserSchema.path('username').required(customUsersValidators.isUserUsernameValid)
33 list, 47// UserSchema.path('role').validate(customUsersValidators.isUserRoleValid)
34 listForApi,
35 loadById,
36 loadByUsername
37}
38 48
39UserSchema.pre('save', function (next) { 49function beforeCreateOrUpdate (user, options, next) {
40 const user = this 50 peertubeCrypto.cryptPassword(user.password, function (err, hash) {
41
42 peertubeCrypto.cryptPassword(this.password, function (err, hash) {
43 if (err) return next(err) 51 if (err) return next(err)
44 52
45 user.password = hash 53 user.password = hash
46 54
47 return next() 55 return next()
48 }) 56 })
49}) 57}
50
51UserSchema.pre('remove', function (next) {
52 const user = this
53
54 OAuthToken.removeByUserId(user._id, next)
55})
56
57mongoose.model('User', UserSchema)
58 58
59// ------------------------------ METHODS ------------------------------ 59// ------------------------------ METHODS ------------------------------
60 60
@@ -64,35 +64,63 @@ function isPasswordMatch (password, callback) {
64 64
65function toFormatedJSON () { 65function toFormatedJSON () {
66 return { 66 return {
67 id: this._id, 67 id: this.id,
68 username: this.username, 68 username: this.username,
69 role: this.role, 69 role: this.role,
70 createdDate: this.createdDate 70 createdAt: this.createdAt
71 } 71 }
72} 72}
73// ------------------------------ STATICS ------------------------------ 73// ------------------------------ STATICS ------------------------------
74 74
75function associate (models) {
76 this.hasMany(models.OAuthToken, {
77 foreignKey: 'userId',
78 onDelete: 'cascade'
79 })
80}
81
75function countTotal (callback) { 82function countTotal (callback) {
76 return this.count(callback) 83 return this.count().asCallback(callback)
77} 84}
78 85
79function getByUsername (username) { 86function getByUsername (username) {
80 return this.findOne({ username: username }) 87 const query = {
88 where: {
89 username: username
90 }
91 }
92
93 return this.findOne(query)
81} 94}
82 95
83function list (callback) { 96function list (callback) {
84 return this.find(callback) 97 return this.find().asCallback(callback)
85} 98}
86 99
87function listForApi (start, count, sort, callback) { 100function listForApi (start, count, sort, callback) {
88 const query = {} 101 const query = {
89 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 102 offset: start,
103 limit: count,
104 order: [ modelUtils.getSort(sort) ]
105 }
106
107 return this.findAndCountAll(query).asCallback(function (err, result) {
108 if (err) return callback(err)
109
110 return callback(null, result.rows, result.count)
111 })
90} 112}
91 113
92function loadById (id, callback) { 114function loadById (id, callback) {
93 return this.findById(id, callback) 115 return this.findById(id).asCallback(callback)
94} 116}
95 117
96function loadByUsername (username, callback) { 118function loadByUsername (username, callback) {
97 return this.findOne({ username: username }, callback) 119 const query = {
120 where: {
121 username: username
122 }
123 }
124
125 return this.findOne(query).asCallback(callback)
98} 126}
diff --git a/server/models/utils.js b/server/models/utils.js
index e798aabe6..49636b3d8 100644
--- a/server/models/utils.js
+++ b/server/models/utils.js
@@ -1,28 +1,23 @@
1'use strict' 1'use strict'
2 2
3const parallel = require('async/parallel')
4
5const utils = { 3const utils = {
6 listForApiWithCount 4 getSort
7} 5}
8 6
9function listForApiWithCount (query, start, count, sort, callback) { 7// Translate for example "-name" to [ 'name', 'DESC' ]
10 const self = this 8function getSort (value) {
9 let field
10 let direction
11 11
12 parallel([ 12 if (value.substring(0, 1) === '-') {
13 function (asyncCallback) { 13 direction = 'DESC'
14 self.find(query).skip(start).limit(count).sort(sort).exec(asyncCallback) 14 field = value.substring(1)
15 }, 15 } else {
16 function (asyncCallback) { 16 direction = 'ASC'
17 self.count(query, asyncCallback) 17 field = value
18 } 18 }
19 ], function (err, results) {
20 if (err) return callback(err)
21 19
22 const data = results[0] 20 return [ field, direction ]
23 const total = results[1]
24 return callback(null, data, total)
25 })
26} 21}
27 22
28// --------------------------------------------------------------------------- 23// ---------------------------------------------------------------------------
diff --git a/server/models/video.js b/server/models/video.js
index 330067cdf..8ef07c9e6 100644
--- a/server/models/video.js
+++ b/server/models/video.js
@@ -7,102 +7,93 @@ const magnetUtil = require('magnet-uri')
7const parallel = require('async/parallel') 7const parallel = require('async/parallel')
8const parseTorrent = require('parse-torrent') 8const parseTorrent = require('parse-torrent')
9const pathUtils = require('path') 9const pathUtils = require('path')
10const mongoose = require('mongoose')
11 10
12const constants = require('../initializers/constants') 11const constants = require('../initializers/constants')
13const customVideosValidators = require('../helpers/custom-validators').videos
14const logger = require('../helpers/logger') 12const logger = require('../helpers/logger')
15const modelUtils = require('./utils') 13const modelUtils = require('./utils')
16 14
17// --------------------------------------------------------------------------- 15// ---------------------------------------------------------------------------
18 16
17module.exports = function (sequelize, DataTypes) {
19// TODO: add indexes on searchable columns 18// TODO: add indexes on searchable columns
20const VideoSchema = mongoose.Schema({ 19 const Video = sequelize.define('Video',
21 name: String, 20 {
22 extname: { 21 id: {
23 type: String, 22 type: DataTypes.UUID,
24 enum: [ '.mp4', '.webm', '.ogv' ] 23 defaultValue: DataTypes.UUIDV4,
25 }, 24 primaryKey: true
26 remoteId: mongoose.Schema.Types.ObjectId,
27 description: String,
28 magnet: {
29 infoHash: String
30 },
31 podHost: String,
32 author: String,
33 duration: Number,
34 tags: [ String ],
35 createdDate: {
36 type: Date,
37 default: Date.now
38 }
39})
40
41VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
42VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
43VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
44VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
45VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
46VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
47
48VideoSchema.methods = {
49 generateMagnetUri,
50 getVideoFilename,
51 getThumbnailName,
52 getPreviewName,
53 getTorrentName,
54 isOwned,
55 toFormatedJSON,
56 toRemoteJSON
57}
58
59VideoSchema.statics = {
60 generateThumbnailFromBase64,
61 getDurationFromFile,
62 listForApi,
63 listByHostAndRemoteId,
64 listByHost,
65 listOwned,
66 listOwnedByAuthor,
67 listRemotes,
68 load,
69 search
70}
71
72VideoSchema.pre('remove', function (next) {
73 const video = this
74 const tasks = []
75
76 tasks.push(
77 function (callback) {
78 removeThumbnail(video, callback)
79 }
80 )
81
82 if (video.isOwned()) {
83 tasks.push(
84 function (callback) {
85 removeFile(video, callback)
86 }, 25 },
87 function (callback) { 26 name: {
88 removeTorrent(video, callback) 27 type: DataTypes.STRING
89 }, 28 },
90 function (callback) { 29 extname: {
91 removePreview(video, callback) 30 // TODO: enum?
31 type: DataTypes.STRING
32 },
33 remoteId: {
34 type: DataTypes.UUID
35 },
36 description: {
37 type: DataTypes.STRING
38 },
39 infoHash: {
40 type: DataTypes.STRING
41 },
42 duration: {
43 type: DataTypes.INTEGER
44 },
45 tags: {
46 type: DataTypes.ARRAY(DataTypes.STRING)
92 } 47 }
93 ) 48 },
94 } 49 {
50 classMethods: {
51 associate,
52
53 generateThumbnailFromBase64,
54 getDurationFromFile,
55 listForApi,
56 listByHostAndRemoteId,
57 listOwnedAndPopulateAuthor,
58 listOwnedByAuthor,
59 load,
60 loadAndPopulateAuthor,
61 loadAndPopulateAuthorAndPod,
62 searchAndPopulateAuthorAndPod
63 },
64 instanceMethods: {
65 generateMagnetUri,
66 getVideoFilename,
67 getThumbnailName,
68 getPreviewName,
69 getTorrentName,
70 isOwned,
71 toFormatedJSON,
72 toRemoteJSON
73 },
74 hooks: {
75 beforeCreate,
76 afterDestroy
77 }
78 }
79 )
95 80
96 parallel(tasks, next) 81 return Video
97}) 82}
98 83
99VideoSchema.pre('save', function (next) { 84// TODO: Validation
100 const video = this 85// VideoSchema.path('name').validate(customVideosValidators.isVideoNameValid)
86// VideoSchema.path('description').validate(customVideosValidators.isVideoDescriptionValid)
87// VideoSchema.path('podHost').validate(customVideosValidators.isVideoPodHostValid)
88// VideoSchema.path('author').validate(customVideosValidators.isVideoAuthorValid)
89// VideoSchema.path('duration').validate(customVideosValidators.isVideoDurationValid)
90// VideoSchema.path('tags').validate(customVideosValidators.isVideoTagsValid)
91
92function beforeCreate (video, options, next) {
101 const tasks = [] 93 const tasks = []
102 94
103 if (video.isOwned()) { 95 if (video.isOwned()) {
104 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename()) 96 const videoPath = pathUtils.join(constants.CONFIG.STORAGE.VIDEOS_DIR, video.getVideoFilename())
105 this.podHost = constants.CONFIG.WEBSERVER.HOST
106 97
107 tasks.push( 98 tasks.push(
108 // TODO: refractoring 99 // TODO: refractoring
@@ -123,7 +114,7 @@ VideoSchema.pre('save', function (next) {
123 if (err) return callback(err) 114 if (err) return callback(err)
124 115
125 const parsedTorrent = parseTorrent(torrent) 116 const parsedTorrent = parseTorrent(torrent)
126 video.magnet.infoHash = parsedTorrent.infoHash 117 video.infoHash = parsedTorrent.infoHash
127 118
128 callback(null) 119 callback(null)
129 }) 120 })
@@ -141,12 +132,46 @@ VideoSchema.pre('save', function (next) {
141 } 132 }
142 133
143 return next() 134 return next()
144}) 135}
136
137function afterDestroy (video, options, next) {
138 const tasks = []
145 139
146mongoose.model('Video', VideoSchema) 140 tasks.push(
141 function (callback) {
142 removeThumbnail(video, callback)
143 }
144 )
145
146 if (video.isOwned()) {
147 tasks.push(
148 function (callback) {
149 removeFile(video, callback)
150 },
151 function (callback) {
152 removeTorrent(video, callback)
153 },
154 function (callback) {
155 removePreview(video, callback)
156 }
157 )
158 }
159
160 parallel(tasks, next)
161}
147 162
148// ------------------------------ METHODS ------------------------------ 163// ------------------------------ METHODS ------------------------------
149 164
165function associate (models) {
166 this.belongsTo(models.Author, {
167 foreignKey: {
168 name: 'authorId',
169 allowNull: false
170 },
171 onDelete: 'cascade'
172 })
173}
174
150function generateMagnetUri () { 175function generateMagnetUri () {
151 let baseUrlHttp, baseUrlWs 176 let baseUrlHttp, baseUrlWs
152 177
@@ -154,8 +179,8 @@ function generateMagnetUri () {
154 baseUrlHttp = constants.CONFIG.WEBSERVER.URL 179 baseUrlHttp = constants.CONFIG.WEBSERVER.URL
155 baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT 180 baseUrlWs = constants.CONFIG.WEBSERVER.WS + '://' + constants.CONFIG.WEBSERVER.HOSTNAME + ':' + constants.CONFIG.WEBSERVER.PORT
156 } else { 181 } else {
157 baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.podHost 182 baseUrlHttp = constants.REMOTE_SCHEME.HTTP + '://' + this.Author.Pod.host
158 baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.podHost 183 baseUrlWs = constants.REMOTE_SCHEME.WS + '://' + this.Author.Pod.host
159 } 184 }
160 185
161 const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName() 186 const xs = baseUrlHttp + constants.STATIC_PATHS.TORRENTS + this.getTorrentName()
@@ -166,7 +191,7 @@ function generateMagnetUri () {
166 xs, 191 xs,
167 announce, 192 announce,
168 urlList, 193 urlList,
169 infoHash: this.magnet.infoHash, 194 infoHash: this.infoHash,
170 name: this.name 195 name: this.name
171 } 196 }
172 197
@@ -174,20 +199,20 @@ function generateMagnetUri () {
174} 199}
175 200
176function getVideoFilename () { 201function getVideoFilename () {
177 if (this.isOwned()) return this._id + this.extname 202 if (this.isOwned()) return this.id + this.extname
178 203
179 return this.remoteId + this.extname 204 return this.remoteId + this.extname
180} 205}
181 206
182function getThumbnailName () { 207function getThumbnailName () {
183 // We always have a copy of the thumbnail 208 // We always have a copy of the thumbnail
184 return this._id + '.jpg' 209 return this.id + '.jpg'
185} 210}
186 211
187function getPreviewName () { 212function getPreviewName () {
188 const extension = '.jpg' 213 const extension = '.jpg'
189 214
190 if (this.isOwned()) return this._id + extension 215 if (this.isOwned()) return this.id + extension
191 216
192 return this.remoteId + extension 217 return this.remoteId + extension
193} 218}
@@ -195,7 +220,7 @@ function getPreviewName () {
195function getTorrentName () { 220function getTorrentName () {
196 const extension = '.torrent' 221 const extension = '.torrent'
197 222
198 if (this.isOwned()) return this._id + extension 223 if (this.isOwned()) return this.id + extension
199 224
200 return this.remoteId + extension 225 return this.remoteId + extension
201} 226}
@@ -205,18 +230,27 @@ function isOwned () {
205} 230}
206 231
207function toFormatedJSON () { 232function toFormatedJSON () {
233 let podHost
234
235 if (this.Author.Pod) {
236 podHost = this.Author.Pod.host
237 } else {
238 // It means it's our video
239 podHost = constants.CONFIG.WEBSERVER.HOST
240 }
241
208 const json = { 242 const json = {
209 id: this._id, 243 id: this.id,
210 name: this.name, 244 name: this.name,
211 description: this.description, 245 description: this.description,
212 podHost: this.podHost, 246 podHost,
213 isLocal: this.isOwned(), 247 isLocal: this.isOwned(),
214 magnetUri: this.generateMagnetUri(), 248 magnetUri: this.generateMagnetUri(),
215 author: this.author, 249 author: this.Author.name,
216 duration: this.duration, 250 duration: this.duration,
217 tags: this.tags, 251 tags: this.tags,
218 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(), 252 thumbnailPath: constants.STATIC_PATHS.THUMBNAILS + '/' + this.getThumbnailName(),
219 createdDate: this.createdDate 253 createdAt: this.createdAt
220 } 254 }
221 255
222 return json 256 return json
@@ -236,13 +270,13 @@ function toRemoteJSON (callback) {
236 const remoteVideo = { 270 const remoteVideo = {
237 name: self.name, 271 name: self.name,
238 description: self.description, 272 description: self.description,
239 magnet: self.magnet, 273 infoHash: self.infoHash,
240 remoteId: self._id, 274 remoteId: self.id,
241 author: self.author, 275 author: self.Author.name,
242 duration: self.duration, 276 duration: self.duration,
243 thumbnailBase64: new Buffer(thumbnailData).toString('base64'), 277 thumbnailBase64: new Buffer(thumbnailData).toString('base64'),
244 tags: self.tags, 278 tags: self.tags,
245 createdDate: self.createdDate, 279 createdAt: self.createdAt,
246 extname: self.extname 280 extname: self.extname
247 } 281 }
248 282
@@ -273,50 +307,168 @@ function getDurationFromFile (videoPath, callback) {
273} 307}
274 308
275function listForApi (start, count, sort, callback) { 309function listForApi (start, count, sort, callback) {
276 const query = {} 310 const query = {
277 return modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 311 offset: start,
312 limit: count,
313 order: [ modelUtils.getSort(sort) ],
314 include: [
315 {
316 model: this.sequelize.models.Author,
317 include: [ this.sequelize.models.Pod ]
318 }
319 ]
320 }
321
322 return this.findAndCountAll(query).asCallback(function (err, result) {
323 if (err) return callback(err)
324
325 return callback(null, result.rows, result.count)
326 })
278} 327}
279 328
280function listByHostAndRemoteId (fromHost, remoteId, callback) { 329function listByHostAndRemoteId (fromHost, remoteId, callback) {
281 this.find({ podHost: fromHost, remoteId: remoteId }, callback) 330 const query = {
282} 331 where: {
332 remoteId: remoteId
333 },
334 include: [
335 {
336 model: this.sequelize.models.Author,
337 include: [
338 {
339 model: this.sequelize.models.Pod,
340 where: {
341 host: fromHost
342 }
343 }
344 ]
345 }
346 ]
347 }
283 348
284function listByHost (fromHost, callback) { 349 return this.findAll(query).asCallback(callback)
285 this.find({ podHost: fromHost }, callback)
286} 350}
287 351
288function listOwned (callback) { 352function listOwnedAndPopulateAuthor (callback) {
289 // If remoteId is null this is *our* video 353 // If remoteId is null this is *our* video
290 this.find({ remoteId: null }, callback) 354 const query = {
355 where: {
356 remoteId: null
357 },
358 include: [ this.sequelize.models.Author ]
359 }
360
361 return this.findAll(query).asCallback(callback)
291} 362}
292 363
293function listOwnedByAuthor (author, callback) { 364function listOwnedByAuthor (author, callback) {
294 this.find({ remoteId: null, author: author }, callback) 365 const query = {
295} 366 where: {
367 remoteId: null
368 },
369 include: [
370 {
371 model: this.sequelize.models.Author,
372 where: {
373 name: author
374 }
375 }
376 ]
377 }
296 378
297function listRemotes (callback) { 379 return this.findAll(query).asCallback(callback)
298 this.find({ remoteId: { $ne: null } }, callback)
299} 380}
300 381
301function load (id, callback) { 382function load (id, callback) {
302 this.findById(id, callback) 383 return this.findById(id).asCallback(callback)
303} 384}
304 385
305function search (value, field, start, count, sort, callback) { 386function loadAndPopulateAuthor (id, callback) {
306 const query = {} 387 const options = {
388 include: [ this.sequelize.models.Author ]
389 }
390
391 return this.findById(id, options).asCallback(callback)
392}
393
394function loadAndPopulateAuthorAndPod (id, callback) {
395 const options = {
396 include: [
397 {
398 model: this.sequelize.models.Author,
399 include: [ this.sequelize.models.Pod ]
400 }
401 ]
402 }
403
404 return this.findById(id, options).asCallback(callback)
405}
406
407function searchAndPopulateAuthorAndPod (value, field, start, count, sort, callback) {
408 const podInclude = {
409 model: this.sequelize.models.Pod
410 }
411 const authorInclude = {
412 model: this.sequelize.models.Author,
413 include: [
414 podInclude
415 ]
416 }
417
418 const query = {
419 where: {},
420 include: [
421 authorInclude
422 ],
423 offset: start,
424 limit: count,
425 order: [ modelUtils.getSort(sort) ]
426 }
427
428 // TODO: include our pod for podHost searches (we are not stored in the database)
307 // Make an exact search with the magnet 429 // Make an exact search with the magnet
308 if (field === 'magnetUri') { 430 if (field === 'magnetUri') {
309 const infoHash = magnetUtil.decode(value).infoHash 431 const infoHash = magnetUtil.decode(value).infoHash
310 query.magnet = { 432 query.where.infoHash = infoHash
311 infoHash
312 }
313 } else if (field === 'tags') { 433 } else if (field === 'tags') {
314 query[field] = value 434 query.where[field] = value
435 } else if (field === 'host') {
436 const whereQuery = {
437 '$Author.Pod.host$': {
438 $like: '%' + value + '%'
439 }
440 }
441
442 // Include our pod? (not stored in the database)
443 if (constants.CONFIG.WEBSERVER.HOST.indexOf(value) !== -1) {
444 query.where = {
445 $or: [
446 whereQuery,
447 {
448 remoteId: null
449 }
450 ]
451 }
452 } else {
453 query.where = whereQuery
454 }
455 } else if (field === 'author') {
456 query.where = {
457 '$Author.name$': {
458 $like: '%' + value + '%'
459 }
460 }
315 } else { 461 } else {
316 query[field] = new RegExp(value, 'i') 462 query.where[field] = {
463 $like: '%' + value + '%'
464 }
317 } 465 }
318 466
319 modelUtils.listForApiWithCount.call(this, query, start, count, sort, callback) 467 return this.findAndCountAll(query).asCallback(function (err, result) {
468 if (err) return callback(err)
469
470 return callback(null, result.rows, result.count)
471 })
320} 472}
321 473
322// --------------------------------------------------------------------------- 474// ---------------------------------------------------------------------------
diff --git a/server/tests/api/check-params.js b/server/tests/api/check-params.js
index 444c2fc55..d9e51770c 100644
--- a/server/tests/api/check-params.js
+++ b/server/tests/api/check-params.js
@@ -465,7 +465,7 @@ describe('Test parameters validator', function () {
465 465
466 it('Should return 404 with an incorrect video', function (done) { 466 it('Should return 404 with an incorrect video', function (done) {
467 request(server.url) 467 request(server.url)
468 .get(path + '123456789012345678901234') 468 .get(path + '4da6fde3-88f7-4d16-b119-108df5630b06')
469 .set('Accept', 'application/json') 469 .set('Accept', 'application/json')
470 .expect(404, done) 470 .expect(404, done)
471 }) 471 })
@@ -490,7 +490,7 @@ describe('Test parameters validator', function () {
490 490
491 it('Should fail with a video which does not exist', function (done) { 491 it('Should fail with a video which does not exist', function (done) {
492 request(server.url) 492 request(server.url)
493 .delete(path + '123456789012345678901234') 493 .delete(path + '4da6fde3-88f7-4d16-b119-108df5630b06')
494 .set('Authorization', 'Bearer ' + server.accessToken) 494 .set('Authorization', 'Bearer ' + server.accessToken)
495 .expect(404, done) 495 .expect(404, done)
496 }) 496 })
@@ -711,7 +711,7 @@ describe('Test parameters validator', function () {
711 711
712 it('Should return 404 with a non existing id', function (done) { 712 it('Should return 404 with a non existing id', function (done) {
713 request(server.url) 713 request(server.url)
714 .delete(path + '579f982228c99c221d8092b8') 714 .delete(path + '45')
715 .set('Authorization', 'Bearer ' + server.accessToken) 715 .set('Authorization', 'Bearer ' + server.accessToken)
716 .expect(404, done) 716 .expect(404, done)
717 }) 717 })
diff --git a/server/tests/api/friends-basic.js b/server/tests/api/friends-basic.js
index a871f9838..3a904dbd7 100644
--- a/server/tests/api/friends-basic.js
+++ b/server/tests/api/friends-basic.js
@@ -97,7 +97,7 @@ describe('Test basic friends', function () {
97 const pod = result[0] 97 const pod = result[0]
98 expect(pod.host).to.equal(servers[2].host) 98 expect(pod.host).to.equal(servers[2].host)
99 expect(pod.score).to.equal(20) 99 expect(pod.score).to.equal(20)
100 expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true 100 expect(miscsUtils.dateIsValid(pod.createdAt)).to.be.true
101 101
102 next() 102 next()
103 }) 103 })
@@ -114,7 +114,7 @@ describe('Test basic friends', function () {
114 const pod = result[0] 114 const pod = result[0]
115 expect(pod.host).to.equal(servers[1].host) 115 expect(pod.host).to.equal(servers[1].host)
116 expect(pod.score).to.equal(20) 116 expect(pod.score).to.equal(20)
117 expect(miscsUtils.dateIsValid(pod.createdDate)).to.be.true 117 expect(miscsUtils.dateIsValid(pod.createdAt)).to.be.true
118 118
119 next() 119 next()
120 }) 120 })
diff --git a/server/tests/api/multiple-pods.js b/server/tests/api/multiple-pods.js
index be278d7c5..f0fe59c5f 100644
--- a/server/tests/api/multiple-pods.js
+++ b/server/tests/api/multiple-pods.js
@@ -104,7 +104,7 @@ describe('Test multiple pods', function () {
104 expect(video.magnetUri).to.exist 104 expect(video.magnetUri).to.exist
105 expect(video.duration).to.equal(10) 105 expect(video.duration).to.equal(10)
106 expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ]) 106 expect(video.tags).to.deep.equal([ 'tag1p1', 'tag2p1' ])
107 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 107 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
108 expect(video.author).to.equal('root') 108 expect(video.author).to.equal('root')
109 109
110 if (server.url !== 'http://localhost:9001') { 110 if (server.url !== 'http://localhost:9001') {
@@ -166,7 +166,7 @@ describe('Test multiple pods', function () {
166 expect(video.magnetUri).to.exist 166 expect(video.magnetUri).to.exist
167 expect(video.duration).to.equal(5) 167 expect(video.duration).to.equal(5)
168 expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ]) 168 expect(video.tags).to.deep.equal([ 'tag1p2', 'tag2p2', 'tag3p2' ])
169 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 169 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
170 expect(video.author).to.equal('root') 170 expect(video.author).to.equal('root')
171 171
172 if (server.url !== 'http://localhost:9002') { 172 if (server.url !== 'http://localhost:9002') {
@@ -246,7 +246,7 @@ describe('Test multiple pods', function () {
246 expect(video1.duration).to.equal(5) 246 expect(video1.duration).to.equal(5)
247 expect(video1.tags).to.deep.equal([ 'tag1p3' ]) 247 expect(video1.tags).to.deep.equal([ 'tag1p3' ])
248 expect(video1.author).to.equal('root') 248 expect(video1.author).to.equal('root')
249 expect(miscsUtils.dateIsValid(video1.createdDate)).to.be.true 249 expect(miscsUtils.dateIsValid(video1.createdAt)).to.be.true
250 250
251 expect(video2.name).to.equal('my super name for pod 3-2') 251 expect(video2.name).to.equal('my super name for pod 3-2')
252 expect(video2.description).to.equal('my super description for pod 3-2') 252 expect(video2.description).to.equal('my super description for pod 3-2')
@@ -255,7 +255,7 @@ describe('Test multiple pods', function () {
255 expect(video2.duration).to.equal(5) 255 expect(video2.duration).to.equal(5)
256 expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ]) 256 expect(video2.tags).to.deep.equal([ 'tag2p3', 'tag3p3', 'tag4p3' ])
257 expect(video2.author).to.equal('root') 257 expect(video2.author).to.equal('root')
258 expect(miscsUtils.dateIsValid(video2.createdDate)).to.be.true 258 expect(miscsUtils.dateIsValid(video2.createdAt)).to.be.true
259 259
260 if (server.url !== 'http://localhost:9003') { 260 if (server.url !== 'http://localhost:9003') {
261 expect(video1.isLocal).to.be.false 261 expect(video1.isLocal).to.be.false
diff --git a/server/tests/api/requests.js b/server/tests/api/requests.js
index af36f6e34..7e790b54b 100644
--- a/server/tests/api/requests.js
+++ b/server/tests/api/requests.js
@@ -69,7 +69,7 @@ describe('Test requests stats', function () {
69 }) 69 })
70 }) 70 })
71 71
72 it('Should have the correct request', function (done) { 72 it('Should have the correct total request', function (done) {
73 this.timeout(15000) 73 this.timeout(15000)
74 74
75 const server = servers[0] 75 const server = servers[0]
@@ -83,11 +83,7 @@ describe('Test requests stats', function () {
83 if (err) throw err 83 if (err) throw err
84 84
85 const body = res.body 85 const body = res.body
86 expect(body.requests).to.have.lengthOf(1) 86 expect(body.totalRequests).to.equal(1)
87
88 const request = body.requests[0]
89 expect(request.to).to.have.lengthOf(1)
90 expect(request.request.type).to.equal('add')
91 87
92 // Wait one cycle 88 // Wait one cycle
93 setTimeout(done, 10000) 89 setTimeout(done, 10000)
@@ -95,27 +91,6 @@ describe('Test requests stats', function () {
95 }) 91 })
96 }) 92 })
97 93
98 it('Should have the correct requests', function (done) {
99 const server = servers[0]
100
101 uploadVideo(server, function (err) {
102 if (err) throw err
103
104 getRequestsStats(server, function (err, res) {
105 if (err) throw err
106
107 const body = res.body
108 expect(body.requests).to.have.lengthOf(2)
109
110 const request = body.requests[1]
111 expect(request.to).to.have.lengthOf(1)
112 expect(request.request.type).to.equal('add')
113
114 done()
115 })
116 })
117 })
118
119 after(function (done) { 94 after(function (done) {
120 process.kill(-servers[0].app.pid) 95 process.kill(-servers[0].app.pid)
121 96
diff --git a/server/tests/api/single-pod.js b/server/tests/api/single-pod.js
index 65d1a7a65..aedecacf3 100644
--- a/server/tests/api/single-pod.js
+++ b/server/tests/api/single-pod.js
@@ -82,7 +82,7 @@ describe('Test a single pod', function () {
82 expect(video.author).to.equal('root') 82 expect(video.author).to.equal('root')
83 expect(video.isLocal).to.be.true 83 expect(video.isLocal).to.be.true
84 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 84 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
85 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 85 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
86 86
87 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 87 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
88 if (err) throw err 88 if (err) throw err
@@ -116,7 +116,7 @@ describe('Test a single pod', function () {
116 expect(video.author).to.equal('root') 116 expect(video.author).to.equal('root')
117 expect(video.isLocal).to.be.true 117 expect(video.isLocal).to.be.true
118 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 118 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
119 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 119 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
120 120
121 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 121 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
122 if (err) throw err 122 if (err) throw err
@@ -142,7 +142,7 @@ describe('Test a single pod', function () {
142 expect(video.author).to.equal('root') 142 expect(video.author).to.equal('root')
143 expect(video.isLocal).to.be.true 143 expect(video.isLocal).to.be.true
144 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 144 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
145 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 145 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
146 146
147 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 147 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
148 if (err) throw err 148 if (err) throw err
@@ -154,7 +154,7 @@ describe('Test a single pod', function () {
154 }) 154 })
155 155
156 it('Should search the video by podHost', function (done) { 156 it('Should search the video by podHost', function (done) {
157 videosUtils.searchVideo(server.url, '9001', 'podHost', function (err, res) { 157 videosUtils.searchVideo(server.url, '9001', 'host', function (err, res) {
158 if (err) throw err 158 if (err) throw err
159 159
160 expect(res.body.total).to.equal(1) 160 expect(res.body.total).to.equal(1)
@@ -168,7 +168,7 @@ describe('Test a single pod', function () {
168 expect(video.author).to.equal('root') 168 expect(video.author).to.equal('root')
169 expect(video.isLocal).to.be.true 169 expect(video.isLocal).to.be.true
170 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 170 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
171 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 171 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
172 172
173 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 173 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
174 if (err) throw err 174 if (err) throw err
@@ -194,7 +194,7 @@ describe('Test a single pod', function () {
194 expect(video.author).to.equal('root') 194 expect(video.author).to.equal('root')
195 expect(video.isLocal).to.be.true 195 expect(video.isLocal).to.be.true
196 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ]) 196 expect(video.tags).to.deep.equal([ 'tag1', 'tag2', 'tag3' ])
197 expect(miscsUtils.dateIsValid(video.createdDate)).to.be.true 197 expect(miscsUtils.dateIsValid(video.createdAt)).to.be.true
198 198
199 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) { 199 videosUtils.testVideoImage(server.url, 'video_short.webm', video.thumbnailPath, function (err, test) {
200 if (err) throw err 200 if (err) throw err
@@ -425,7 +425,7 @@ describe('Test a single pod', function () {
425 }) 425 })
426 426
427 it('Should search all the 9001 port videos', function (done) { 427 it('Should search all the 9001 port videos', function (done) {
428 videosUtils.searchVideoWithPagination(server.url, '9001', 'podHost', 0, 15, function (err, res) { 428 videosUtils.searchVideoWithPagination(server.url, '9001', 'host', 0, 15, function (err, res) {
429 if (err) throw err 429 if (err) throw err
430 430
431 const videos = res.body.data 431 const videos = res.body.data
@@ -437,7 +437,7 @@ describe('Test a single pod', function () {
437 }) 437 })
438 438
439 it('Should search all the localhost videos', function (done) { 439 it('Should search all the localhost videos', function (done) {
440 videosUtils.searchVideoWithPagination(server.url, 'localhost', 'podHost', 0, 15, function (err, res) { 440 videosUtils.searchVideoWithPagination(server.url, 'localhost', 'host', 0, 15, function (err, res) {
441 if (err) throw err 441 if (err) throw err
442 442
443 const videos = res.body.data 443 const videos = res.body.data
diff --git a/server/tests/api/users.js b/server/tests/api/users.js
index 94267f104..e6d937eb0 100644
--- a/server/tests/api/users.js
+++ b/server/tests/api/users.js
@@ -261,8 +261,8 @@ describe('Test users', function () {
261 }) 261 })
262 }) 262 })
263 263
264 it('Should list only the second user by createdDate desc', function (done) { 264 it('Should list only the second user by createdAt desc', function (done) {
265 usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdDate', function (err, res) { 265 usersUtils.getUsersListPaginationAndSort(server.url, 0, 1, '-createdAt', function (err, res) {
266 if (err) throw err 266 if (err) throw err
267 267
268 const result = res.body 268 const result = res.body
@@ -279,8 +279,8 @@ describe('Test users', function () {
279 }) 279 })
280 }) 280 })
281 281
282 it('Should list all the users by createdDate asc', function (done) { 282 it('Should list all the users by createdAt asc', function (done) {
283 usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdDate', function (err, res) { 283 usersUtils.getUsersListPaginationAndSort(server.url, 0, 2, 'createdAt', function (err, res) {
284 if (err) throw err 284 if (err) throw err
285 285
286 const result = res.body 286 const result = res.body
diff --git a/server/tests/utils/servers.js b/server/tests/utils/servers.js
index 01c9a2f39..4e55f8f5c 100644
--- a/server/tests/utils/servers.js
+++ b/server/tests/utils/servers.js
@@ -60,12 +60,12 @@ function runServer (number, callback) {
60 60
61 // These actions are async so we need to be sure that they have both been done 61 // These actions are async so we need to be sure that they have both been done
62 const serverRunString = { 62 const serverRunString = {
63 'Connected to mongodb': false, 63 'Database is ready': false,
64 'Server listening on port': false 64 'Server listening on port': false
65 } 65 }
66 66
67 const regexps = { 67 const regexps = {
68 client_id: 'Client id: ([a-f0-9]+)', 68 client_id: 'Client id: (.+)',
69 client_secret: 'Client secret: (.+)', 69 client_secret: 'Client secret: (.+)',
70 user_username: 'Username: (.+)', 70 user_username: 'Username: (.+)',
71 user_password: 'User password: (.+)' 71 user_password: 'User password: (.+)'
diff --git a/server/tests/utils/videos.js b/server/tests/utils/videos.js
index 536093db1..5c120597f 100644
--- a/server/tests/utils/videos.js
+++ b/server/tests/utils/videos.js
@@ -25,7 +25,7 @@ function getAllVideosListBy (url, end) {
25 25
26 request(url) 26 request(url)
27 .get(path) 27 .get(path)
28 .query({ sort: 'createdDate' }) 28 .query({ sort: 'createdAt' })
29 .query({ start: 0 }) 29 .query({ start: 0 })
30 .query({ count: 10000 }) 30 .query({ count: 10000 })
31 .set('Accept', 'application/json') 31 .set('Accept', 'application/json')